From 08c1a30d90fe75506f815843896035def499a889 Mon Sep 17 00:00:00 2001 From: hzzz Date: Tue, 5 Jun 2018 17:02:49 +0800 Subject: [PATCH 01/18] =?UTF-8?q?=E6=B7=BB=E5=8A=A0apache/commons-fileuplo?= =?UTF-8?q?ad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fine-commons-fileupload/.DS_Store | Bin 0 -> 6148 bytes fine-commons-fileupload/.classpath | 8 + fine-commons-fileupload/.project | 23 + .../.settings/org.eclipse.jdt.core.prefs | 74 + .../.settings/org.eclipse.jdt.ui.prefs | 3 + fine-commons-fileupload/src/.DS_Store | Bin 0 -> 6148 bytes .../src/com/fr/third/.DS_Store | Bin 0 -> 6148 bytes .../commons/fileupload/DefaultFileItem.java | 80 ++ .../fileupload/DefaultFileItemFactory.java | 107 ++ .../commons/fileupload/DiskFileUpload.java | 212 +++ .../apache/commons/fileupload/FileItem.java | 226 ++++ .../commons/fileupload/FileItemFactory.java | 50 + .../commons/fileupload/FileItemIterator.java | 48 + .../commons/fileupload/FileItemStream.java | 97 ++ .../apache/commons/fileupload/FileUpload.java | 106 ++ .../commons/fileupload/FileUploadBase.java | 1186 +++++++++++++++++ .../fileupload/FileUploadException.java | 95 ++ .../commons/fileupload/MultipartStream.java | 1047 +++++++++++++++ .../commons/fileupload/ParameterParser.java | 299 +++++ .../commons/fileupload/ProgressListener.java | 34 + .../commons/fileupload/RequestContext.java | 64 + .../commons/fileupload/disk/DiskFileItem.java | 702 ++++++++++ .../fileupload/disk/DiskFileItemFactory.java | 195 +++ .../commons/fileupload/disk/package.html | 58 + .../apache/commons/fileupload/package.html | 90 ++ .../fileupload/portlet/PortletFileUpload.java | 142 ++ .../portlet/PortletRequestContext.java | 108 ++ .../commons/fileupload/portlet/package.html | 49 + .../servlet/FileCleanerCleanup.java | 49 + .../fileupload/servlet/ServletFileUpload.java | 150 +++ .../servlet/ServletRequestContext.java | 107 ++ .../commons/fileupload/servlet/package.html | 49 + .../commons/fileupload/util/Closeable.java | 38 + .../fileupload/util/LimitedInputStream.java | 155 +++ .../commons/fileupload/util/Streams.java | 166 +++ .../commons/fileupload/util/package.html | 29 + 36 files changed, 5846 insertions(+) create mode 100644 fine-commons-fileupload/.DS_Store create mode 100755 fine-commons-fileupload/.classpath create mode 100755 fine-commons-fileupload/.project create mode 100755 fine-commons-fileupload/.settings/org.eclipse.jdt.core.prefs create mode 100755 fine-commons-fileupload/.settings/org.eclipse.jdt.ui.prefs create mode 100644 fine-commons-fileupload/src/.DS_Store create mode 100644 fine-commons-fileupload/src/com/fr/third/.DS_Store create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/DefaultFileItem.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/DefaultFileItemFactory.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/DiskFileUpload.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItem.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemFactory.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemIterator.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemStream.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUpload.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUploadBase.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUploadException.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/MultipartStream.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/ParameterParser.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/ProgressListener.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/RequestContext.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItem.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItemFactory.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/package.html create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/package.html create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/PortletFileUpload.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/PortletRequestContext.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/package.html create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/ServletFileUpload.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/ServletRequestContext.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/package.html create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/Closeable.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/LimitedInputStream.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/Streams.java create mode 100755 fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/package.html diff --git a/fine-commons-fileupload/.DS_Store b/fine-commons-fileupload/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + + + + + + + diff --git a/fine-commons-fileupload/.project b/fine-commons-fileupload/.project new file mode 100755 index 000000000..647b3367a --- /dev/null +++ b/fine-commons-fileupload/.project @@ -0,0 +1,23 @@ + + + commons-fileupload + The FileUpload component provides a simple yet flexible means of adding support for multipart file upload functionality to servlets and web applications. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.maven.ide.eclipse.maven2Nature + + diff --git a/fine-commons-fileupload/.settings/org.eclipse.jdt.core.prefs b/fine-commons-fileupload/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 000000000..f16412c6a --- /dev/null +++ b/fine-commons-fileupload/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,74 @@ +#Mon Aug 07 02:40:54 CEST 2006 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.1 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.3 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=ignore +org.eclipse.jdt.core.compiler.problem.autoboxing=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=disabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.3 diff --git a/fine-commons-fileupload/.settings/org.eclipse.jdt.ui.prefs b/fine-commons-fileupload/.settings/org.eclipse.jdt.ui.prefs new file mode 100755 index 000000000..c8d0df1d6 --- /dev/null +++ b/fine-commons-fileupload/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,3 @@ +#Sun Jun 18 21:36:57 CEST 2006 +eclipse.preferences.version=1 +internal.default.compliance=default diff --git a/fine-commons-fileupload/src/.DS_Store b/fine-commons-fileupload/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..992d869836a6e7daff929b0f46ce1eff98f21e2c GIT binary patch literal 6148 zcmeHKJ5Iwu5S<|@BGIIz+$*G}Sw-dqxd4(aL4oBIq<6rPzB%pOb1+8*r~nn90#x9?D1e@Awz>@DN(HC@ z75GxXz7GX%SQFGSprxr0IZ2~AR;ggDln*;BZdYY@sf2laSjZ+ zXbvBmch;Ox)bEb-i!yIr@%D`jtO yUe0=Lf#1SE4Ygj5#al7ZTQN4)im!gu6}v{hCeDFQN8IT^{s@>ZG%D~L3VZ-s=qHH) literal 0 HcmV?d00001 diff --git a/fine-commons-fileupload/src/com/fr/third/.DS_Store b/fine-commons-fileupload/src/com/fr/third/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7c8577a5fbea0551c707d4d59791d5665a08a0ea GIT binary patch literal 6148 zcmeH~J#GR)427Qq1yY(VDW}N+xWNd)3Ag|S5={gW5~=6tdH!j#NsU&~vt+-qXRY1+ zik&q8+dnTmU;?nAyW+#c%#8U8cRX The default implementation of the + * {@link com.fr.third.org.apache.commons.fileupload.FileItem FileItem} interface. + * + *

After retrieving an instance of this class from a {@link + * com.fr.third.org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see + * {@link com.fr.third.org.apache.commons.fileupload.DiskFileUpload + * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may + * either request all contents of file at once using {@link #get()} or + * request an {@link java.io.InputStream InputStream} with + * {@link #getInputStream()} and process the file without attempting to load + * it into memory, which may come handy with large files. + * + * @author Rafal Krzewski + * @author Sean Legassick + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id$ + * + * @deprecated Use DiskFileItem instead. + */ +public class DefaultFileItem + extends DiskFileItem { + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs a new DefaultFileItem instance. + * + * @param fieldName The name of the form field. + * @param contentType The content type passed by the browser or + * null if not specified. + * @param isFormField Whether or not this item is a plain form field, as + * opposed to a file upload. + * @param fileName The original filename in the user's filesystem, or + * null if not specified. + * @param sizeThreshold The threshold, in bytes, below which items will be + * retained in memory and above which they will be + * stored as a file. + * @param repository The data repository, which is the directory in + * which files will be created, should the item size + * exceed the threshold. + * + * @deprecated Use DiskFileItem instead. + */ + public DefaultFileItem(String fieldName, String contentType, + boolean isFormField, String fileName, int sizeThreshold, + File repository) { + super(fieldName, contentType, isFormField, fileName, sizeThreshold, + repository); + } + + +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/DefaultFileItemFactory.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/DefaultFileItemFactory.java new file mode 100755 index 000000000..5ffdd0d52 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/DefaultFileItemFactory.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + +import java.io.File; +import com.fr.third.org.apache.commons.fileupload.disk.DiskFileItemFactory; + +/** + *

The default {@link com.fr.third.org.apache.commons.fileupload.FileItemFactory} + * implementation. This implementation creates + * {@link com.fr.third.org.apache.commons.fileupload.FileItem} instances which keep their + * content either in memory, for smaller items, or in a temporary file on disk, + * for larger items. The size threshold, above which content will be stored on + * disk, is configurable, as is the directory in which temporary files will be + * created.

+ * + *

If not otherwise configured, the default configuration values are as + * follows: + *

    + *
  • Size threshold is 10KB.
  • + *
  • Repository is the system default temp directory, as returned by + * System.getProperty("java.io.tmpdir").
  • + *
+ *

+ * + * @author Martin Cooper + * + * @version $Id$ + * + * @deprecated Use DiskFileItemFactory instead. + */ +public class DefaultFileItemFactory extends DiskFileItemFactory { + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an unconfigured instance of this class. The resulting factory + * may be configured by calling the appropriate setter methods. + * + * @deprecated Use DiskFileItemFactory instead. + */ + public DefaultFileItemFactory() { + super(); + } + + + /** + * Constructs a preconfigured instance of this class. + * + * @param sizeThreshold The threshold, in bytes, below which items will be + * retained in memory and above which they will be + * stored as a file. + * @param repository The data repository, which is the directory in + * which files will be created, should the item size + * exceed the threshold. + * + * @deprecated Use DiskFileItemFactory instead. + */ + public DefaultFileItemFactory(int sizeThreshold, File repository) { + super(sizeThreshold, repository); + } + + + // --------------------------------------------------------- Public Methods + + /** + * Create a new {@link com.fr.third.org.apache.commons.fileupload.DefaultFileItem} + * instance from the supplied parameters and the local factory + * configuration. + * + * @param fieldName The name of the form field. + * @param contentType The content type of the form field. + * @param isFormField true if this is a plain form field; + * false otherwise. + * @param fileName The name of the uploaded file, if any, as supplied + * by the browser or other client. + * + * @return The newly created file item. + * + * @deprecated Use DiskFileItemFactory instead. + */ + public FileItem createItem( + String fieldName, + String contentType, + boolean isFormField, + String fileName + ) { + return new DefaultFileItem(fieldName, contentType, + isFormField, fileName, getSizeThreshold(), getRepository()); + } + +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/DiskFileUpload.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/DiskFileUpload.java new file mode 100755 index 000000000..420a78273 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/DiskFileUpload.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + +import java.io.File; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * com.fr.third.org.apache.commons.fileupload.FileItem}s associated with a given HTML + * widget.

+ * + *

Individual parts will be stored in temporary disk storage or in memory, + * depending on their size, and will be available as {@link + * com.fr.third.org.apache.commons.fileupload.FileItem}s.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id$ + * + * @deprecated Use ServletFileUpload together with + * DiskFileItemFactory instead. + */ +public class DiskFileUpload + extends FileUploadBase { + + // ----------------------------------------------------------- Data members + + + /** + * The factory to use to create new form items. + */ + private DefaultFileItemFactory fileItemFactory; + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an instance of this class which uses the default factory to + * create FileItem instances. + * + * @see #DiskFileUpload(DefaultFileItemFactory fileItemFactory) + * + * @deprecated Use FileUpload instead. + */ + public DiskFileUpload() { + super(); + this.fileItemFactory = new DefaultFileItemFactory(); + } + + + /** + * Constructs an instance of this class which uses the supplied factory to + * create FileItem instances. + * + * @see #DiskFileUpload() + * @param fileItemFactory The file item factory to use. + * + * @deprecated Use FileUpload instead. + */ + public DiskFileUpload(DefaultFileItemFactory fileItemFactory) { + super(); + this.fileItemFactory = fileItemFactory; + } + + + // ----------------------------------------------------- Property accessors + + + /** + * Returns the factory class used when creating file items. + * + * @return The factory class for new file items. + * + * @deprecated Use FileUpload instead. + */ + public FileItemFactory getFileItemFactory() { + return fileItemFactory; + } + + + /** + * Sets the factory class to use when creating file items. The factory must + * be an instance of DefaultFileItemFactory or a subclass + * thereof, or else a ClassCastException will be thrown. + * + * @param factory The factory class for new file items. + * + * @deprecated Use FileUpload instead. + */ + public void setFileItemFactory(FileItemFactory factory) { + this.fileItemFactory = (DefaultFileItemFactory) factory; + } + + + /** + * Returns the size threshold beyond which files are written directly to + * disk. + * + * @return The size threshold, in bytes. + * + * @see #setSizeThreshold(int) + * + * @deprecated Use DiskFileItemFactory instead. + */ + public int getSizeThreshold() { + return fileItemFactory.getSizeThreshold(); + } + + + /** + * Sets the size threshold beyond which files are written directly to disk. + * + * @param sizeThreshold The size threshold, in bytes. + * + * @see #getSizeThreshold() + * + * @deprecated Use DiskFileItemFactory instead. + */ + public void setSizeThreshold(int sizeThreshold) { + fileItemFactory.setSizeThreshold(sizeThreshold); + } + + + /** + * Returns the location used to temporarily store files that are larger + * than the configured size threshold. + * + * @return The path to the temporary file location. + * + * @see #setRepositoryPath(String) + * + * @deprecated Use DiskFileItemFactory instead. + */ + public String getRepositoryPath() { + return fileItemFactory.getRepository().getPath(); + } + + + /** + * Sets the location used to temporarily store files that are larger + * than the configured size threshold. + * + * @param repositoryPath The path to the temporary file location. + * + * @see #getRepositoryPath() + * + * @deprecated Use DiskFileItemFactory instead. + */ + public void setRepositoryPath(String repositoryPath) { + fileItemFactory.setRepository(new File(repositoryPath)); + } + + + // --------------------------------------------------------- Public methods + + + /** + * Processes an RFC 1867 + * compliant multipart/form-data stream. If files are stored + * on disk, the path is given by getRepository(). + * + * @param req The servlet request to be parsed. Must be non-null. + * @param sizeThreshold The max size in bytes to be stored in memory. + * @param sizeMax The maximum allowed upload size, in bytes. + * @param path The location where the files should be stored. + * + * @return A list of FileItem instances parsed from the + * request, in the order that they were transmitted. + * + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * + * @deprecated Use ServletFileUpload instead. + */ + public List /* FileItem */ parseRequest(HttpServletRequest req, + int sizeThreshold, + long sizeMax, String path) + throws FileUploadException { + setSizeThreshold(sizeThreshold); + setSizeMax(sizeMax); + setRepositoryPath(path); + return parseRequest(req); + } + +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItem.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItem.java new file mode 100755 index 000000000..905180a79 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItem.java @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; + +/** + *

This class represents a file or form item that was received within a + * multipart/form-data POST request. + * + *

After retrieving an instance of this class from a {@link + * com.fr.third.org.apache.commons.fileupload.FileUpload FileUpload} instance (see + * {@link com.fr.third.org.apache.commons.fileupload.FileUpload + * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may + * either request all contents of the file at once using {@link #get()} or + * request an {@link java.io.InputStream InputStream} with + * {@link #getInputStream()} and process the file without attempting to load + * it into memory, which may come handy with large files. + * + *

While this interface does not extend + * javax.activation.DataSource per se (to avoid a seldom used + * dependency), several of the defined methods are specifically defined with + * the same signatures as methods in that interface. This allows an + * implementation of this interface to also implement + * javax.activation.DataSource with minimal additional work. + * + * @author Rafal Krzewski + * @author Sean Legassick + * @author Jason van Zyl + * @author Martin Cooper + * + * @version $Id$ + */ +public interface FileItem + extends Serializable { + + + // ------------------------------- Methods from javax.activation.DataSource + + + /** + * Returns an {@link java.io.InputStream InputStream} that can be + * used to retrieve the contents of the file. + * + * @return An {@link java.io.InputStream InputStream} that can be + * used to retrieve the contents of the file. + * + * @throws IOException if an error occurs. + */ + InputStream getInputStream() + throws IOException; + + + /** + * Returns the content type passed by the browser or null if + * not defined. + * + * @return The content type passed by the browser or null if + * not defined. + */ + String getContentType(); + + + /** + * Returns the original filename in the client's filesystem, as provided by + * the browser (or other client software). In most cases, this will be the + * base file name, without path information. However, some clients, such as + * the Opera browser, do include path information. + * + * @return The original filename in the client's filesystem. + */ + String getName(); + + + // ------------------------------------------------------- FileItem methods + + + /** + * Provides a hint as to whether or not the file contents will be read + * from memory. + * + * @return true if the file contents will be read from memory; + * false otherwise. + */ + boolean isInMemory(); + + + /** + * Returns the size of the file item. + * + * @return The size of the file item, in bytes. + */ + long getSize(); + + + /** + * Returns the contents of the file item as an array of bytes. + * + * @return The contents of the file item as an array of bytes. + */ + byte[] get(); + + + /** + * Returns the contents of the file item as a String, using the specified + * encoding. This method uses {@link #get()} to retrieve the + * contents of the item. + * + * @param encoding The character encoding to use. + * + * @return The contents of the item, as a string. + * + * @throws UnsupportedEncodingException if the requested character + * encoding is not available. + */ + String getString(String encoding) + throws UnsupportedEncodingException; + + + /** + * Returns the contents of the file item as a String, using the default + * character encoding. This method uses {@link #get()} to retrieve the + * contents of the item. + * + * @return The contents of the item, as a string. + */ + String getString(); + + + /** + * A convenience method to write an uploaded item to disk. The client code + * is not concerned with whether or not the item is stored in memory, or on + * disk in a temporary location. They just want to write the uploaded item + * to a file. + *

+ * This method is not guaranteed to succeed if called more than once for + * the same item. This allows a particular implementation to use, for + * example, file renaming, where possible, rather than copying all of the + * underlying data, thus gaining a significant performance benefit. + * + * @param file The File into which the uploaded item should + * be stored. + * + * @throws Exception if an error occurs. + */ + void write(File file) throws Exception; + + + /** + * Deletes the underlying storage for a file item, including deleting any + * associated temporary disk file. Although this storage will be deleted + * automatically when the FileItem instance is garbage + * collected, this method can be used to ensure that this is done at an + * earlier time, thus preserving system resources. + */ + void delete(); + + + /** + * Returns the name of the field in the multipart form corresponding to + * this file item. + * + * @return The name of the form field. + */ + String getFieldName(); + + + /** + * Sets the field name used to reference this file item. + * + * @param name The name of the form field. + */ + void setFieldName(String name); + + + /** + * Determines whether or not a FileItem instance represents + * a simple form field. + * + * @return true if the instance represents a simple form + * field; false if it represents an uploaded file. + */ + boolean isFormField(); + + + /** + * Specifies whether or not a FileItem instance represents + * a simple form field. + * + * @param state true if the instance represents a simple form + * field; false if it represents an uploaded file. + */ + void setFormField(boolean state); + + + /** + * Returns an {@link java.io.OutputStream OutputStream} that can + * be used for storing the contents of the file. + * + * @return An {@link java.io.OutputStream OutputStream} that can be used + * for storing the contensts of the file. + * + * @throws IOException if an error occurs. + */ + OutputStream getOutputStream() throws IOException; + +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemFactory.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemFactory.java new file mode 100755 index 000000000..952189023 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemFactory.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + + +/** + *

A factory interface for creating {@link FileItem} instances. Factories + * can provide their own custom configuration, over and above that provided + * by the default file upload implementation.

+ * + * @author Martin Cooper + * + * @version $Id$ + */ +public interface FileItemFactory { + + /** + * Create a new {@link FileItem} instance from the supplied parameters and + * any local factory configuration. + * + * @param fieldName The name of the form field. + * @param contentType The content type of the form field. + * @param isFormField true if this is a plain form field; + * false otherwise. + * @param fileName The name of the uploaded file, if any, as supplied + * by the browser or other client. + * + * @return The newly created file item. + */ + FileItem createItem( + String fieldName, + String contentType, + boolean isFormField, + String fileName + ); +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemIterator.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemIterator.java new file mode 100755 index 000000000..607f12bdf --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemIterator.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; +import java.io.IOException; + + +/** + * An iterator, as returned by + * {@link FileUploadBase#getItemIterator(RequestContext)}. + */ +public interface FileItemIterator { + /** + * Returns, whether another instance of {@link FileItemStream} + * is available. + * @throws FileUploadException Parsing or processing the + * file item failed. + * @throws IOException Reading the file item failed. + * @return True, if one or more additional file items + * are available, otherwise false. + */ + boolean hasNext() throws FileUploadException, IOException; + + /** + * Returns the next available {@link FileItemStream}. + * @throws java.util.NoSuchElementException No more items are available. Use + * {@link #hasNext()} to prevent this exception. + * @throws FileUploadException Parsing or processing the + * file item failed. + * @throws IOException Reading the file item failed. + * @return FileItemStream instance, which provides + * access to the next file item. + */ + FileItemStream next() throws FileUploadException, IOException; +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemStream.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemStream.java new file mode 100755 index 000000000..a1f0813c4 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileItemStream.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + +import java.io.IOException; +import java.io.InputStream; + + +/** + *

This interface provides access to a file or form item that was + * received within a multipart/form-data POST request. + * The items contents are retrieved by calling {@link #openStream()}.

+ *

Instances of this class are created by accessing the + * iterator, returned by + * {@link FileUploadBase#getItemIterator(RequestContext)}.

+ *

Note: There is an interaction between the iterator and + * its associated instances of {@link FileItemStream}: By invoking + * {@link java.util.Iterator#hasNext()} on the iterator, you discard all data, + * which hasn't been read so far from the previous data.

+ */ +public interface FileItemStream { + /** + * This exception is thrown, if an attempt is made to read + * data from the {@link InputStream}, which has been returned + * by {@link FileItemStream#openStream()}, after + * {@link java.util.Iterator#hasNext()} has been invoked on the + * iterator, which created the {@link FileItemStream}. + */ + public static class ItemSkippedException extends IOException { + /** + * The exceptions serial version UID, which is being used + * when serializing an exception instance. + */ + private static final long serialVersionUID = -7280778431581963740L; + } + + /** Creates an {@link InputStream}, which allows to read the + * items contents. + * @return The input stream, from which the items data may + * be read. + * @throws IllegalStateException The method was already invoked on + * this item. It is not possible to recreate the data stream. + * @throws IOException An I/O error occurred. + * @see ItemSkippedException + */ + InputStream openStream() throws IOException; + + /** + * Returns the content type passed by the browser or null if + * not defined. + * + * @return The content type passed by the browser or null if + * not defined. + */ + String getContentType(); + + /** + * Returns the original filename in the client's filesystem, as provided by + * the browser (or other client software). In most cases, this will be the + * base file name, without path information. However, some clients, such as + * the Opera browser, do include path information. + * + * @return The original filename in the client's filesystem. + */ + String getName(); + + /** + * Returns the name of the field in the multipart form corresponding to + * this file item. + * + * @return The name of the form field. + */ + String getFieldName(); + + /** + * Determines whether or not a FileItem instance represents + * a simple form field. + * + * @return true if the instance represents a simple form + * field; false if it represents an uploaded file. + */ + boolean isFormField(); +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUpload.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUpload.java new file mode 100755 index 000000000..d5fb14cd9 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUpload.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(javax.servlet.http.HttpServletRequest)} to acquire a list + * of {@link com.fr.third.org.apache.commons.fileupload.FileItem FileItems} associated + * with a given HTML widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id$ + */ +public class FileUpload + extends FileUploadBase { + + // ----------------------------------------------------------- Data members + + + /** + * The factory to use to create new form items. + */ + private FileItemFactory fileItemFactory; + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an uninitialised instance of this class. A factory must be + * configured, using setFileItemFactory(), before attempting + * to parse requests. + * + * @see #FileUpload(FileItemFactory) + */ + public FileUpload() { + super(); + } + + + /** + * Constructs an instance of this class which uses the supplied factory to + * create FileItem instances. + * + * @see #FileUpload() + * @param fileItemFactory The factory to use for creating file items. + */ + public FileUpload(FileItemFactory fileItemFactory) { + super(); + this.fileItemFactory = fileItemFactory; + } + + + // ----------------------------------------------------- Property accessors + + + /** + * Returns the factory class used when creating file items. + * + * @return The factory class for new file items. + */ + public FileItemFactory getFileItemFactory() { + return fileItemFactory; + } + + + /** + * Sets the factory class to use when creating file items. + * + * @param factory The factory class for new file items. + */ + public void setFileItemFactory(FileItemFactory factory) { + this.fileItemFactory = factory; + } + + +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUploadBase.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUploadBase.java new file mode 100755 index 000000000..ee9e8290e --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUploadBase.java @@ -0,0 +1,1186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import javax.servlet.http.HttpServletRequest; + +import com.fr.third.org.apache.commons.fileupload.servlet.ServletFileUpload; +import com.fr.third.org.apache.commons.fileupload.servlet.ServletRequestContext; +import com.fr.third.org.apache.commons.fileupload.util.Closeable; +import com.fr.third.org.apache.commons.fileupload.util.LimitedInputStream; +import com.fr.third.org.apache.commons.fileupload.util.Streams; + + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * com.fr.third.org.apache.commons.fileupload.FileItem}s associated with a given HTML + * widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id$ + */ +public abstract class FileUploadBase { + + // ---------------------------------------------------------- Class methods + + + /** + *

Utility method that determines whether the request contains multipart + * content.

+ * + *

NOTE:This method will be moved to the + * ServletFileUpload class after the FileUpload 1.1 release. + * Unfortunately, since this method is static, it is not possible to + * provide its replacement until this method is removed.

+ * + * @param ctx The request context to be evaluated. Must be non-null. + * + * @return true if the request is multipart; + * false otherwise. + */ + public static final boolean isMultipartContent(RequestContext ctx) { + String contentType = ctx.getContentType(); + if (contentType == null) { + return false; + } + if (contentType.toLowerCase().startsWith(MULTIPART)) { + return true; + } + return false; + } + + + /** + * Utility method that determines whether the request contains multipart + * content. + * + * @param req The servlet request to be evaluated. Must be non-null. + * + * @return true if the request is multipart; + * false otherwise. + * + * @deprecated Use the method on ServletFileUpload instead. + */ + public static boolean isMultipartContent(HttpServletRequest req) { + return ServletFileUpload.isMultipartContent(req); + } + + + // ----------------------------------------------------- Manifest constants + + + /** + * HTTP content type header name. + */ + public static final String CONTENT_TYPE = "Content-type"; + + + /** + * HTTP content disposition header name. + */ + public static final String CONTENT_DISPOSITION = "Content-disposition"; + + + /** + * Content-disposition value for form data. + */ + public static final String FORM_DATA = "form-data"; + + + /** + * Content-disposition value for file attachment. + */ + public static final String ATTACHMENT = "attachment"; + + + /** + * Part of HTTP content type header. + */ + public static final String MULTIPART = "multipart/"; + + + /** + * HTTP content type header for multipart forms. + */ + public static final String MULTIPART_FORM_DATA = "multipart/form-data"; + + + /** + * HTTP content type header for multiple uploads. + */ + public static final String MULTIPART_MIXED = "multipart/mixed"; + + + /** + * The maximum length of a single header line that will be parsed + * (1024 bytes). + * @deprecated This constant is no longer used. As of commons-fileupload + * 1.2, the only applicable limit is the total size of a parts headers, + * {@link MultipartStream#HEADER_PART_SIZE_MAX}. + */ + public static final int MAX_HEADER_SIZE = 1024; + + + // ----------------------------------------------------------- Data members + + + /** + * The maximum size permitted for the complete request, as opposed to + * {@link #fileSizeMax}. A value of -1 indicates no maximum. + */ + private long sizeMax = -1; + + /** + * The maximum size permitted for a single uploaded file, as opposed + * to {@link #sizeMax}. A value of -1 indicates no maximum. + */ + private long fileSizeMax = -1; + + /** + * The content encoding to use when reading part headers. + */ + private String headerEncoding; + + /** + * The progress listener. + */ + private ProgressListener listener; + + // ----------------------------------------------------- Property accessors + + + /** + * Returns the factory class used when creating file items. + * + * @return The factory class for new file items. + */ + public abstract FileItemFactory getFileItemFactory(); + + + /** + * Sets the factory class to use when creating file items. + * + * @param factory The factory class for new file items. + */ + public abstract void setFileItemFactory(FileItemFactory factory); + + + /** + * Returns the maximum allowed size of a complete request, as opposed + * to {@link #getFileSizeMax()}. + * + * @return The maximum allowed size, in bytes. The default value of + * -1 indicates, that there is no limit. + * + * @see #setSizeMax(long) + * + */ + public long getSizeMax() { + return sizeMax; + } + + + /** + * Sets the maximum allowed size of a complete request, as opposed + * to {@link #setFileSizeMax(long)}. + * + * @param sizeMax The maximum allowed size, in bytes. The default value of + * -1 indicates, that there is no limit. + * + * @see #getSizeMax() + * + */ + public void setSizeMax(long sizeMax) { + this.sizeMax = sizeMax; + } + + /** + * Returns the maximum allowed size of a single uploaded file, + * as opposed to {@link #getSizeMax()}. + * + * @see #setFileSizeMax(long) + * @return Maximum size of a single uploaded file. + */ + public long getFileSizeMax() { + return fileSizeMax; + } + + /** + * Sets the maximum allowed size of a single uploaded file, + * as opposed to {@link #getSizeMax()}. + * + * @see #getFileSizeMax() + * @param fileSizeMax Maximum size of a single uploaded file. + */ + public void setFileSizeMax(long fileSizeMax) { + this.fileSizeMax = fileSizeMax; + } + + /** + * Retrieves the character encoding used when reading the headers of an + * individual part. When not specified, or null, the request + * encoding is used. If that is also not specified, or null, + * the platform default encoding is used. + * + * @return The encoding used to read part headers. + */ + public String getHeaderEncoding() { + return headerEncoding; + } + + + /** + * Specifies the character encoding to be used when reading the headers of + * individual part. When not specified, or null, the request + * encoding is used. If that is also not specified, or null, + * the platform default encoding is used. + * + * @param encoding The encoding used to read part headers. + */ + public void setHeaderEncoding(String encoding) { + headerEncoding = encoding; + } + + + // --------------------------------------------------------- Public methods + + + /** + * Processes an RFC 1867 + * compliant multipart/form-data stream. + * + * @param req The servlet request to be parsed. + * + * @return A list of FileItem instances parsed from the + * request, in the order that they were transmitted. + * + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * + * @deprecated Use the method in ServletFileUpload instead. + */ + public List /* FileItem */ parseRequest(HttpServletRequest req) + throws FileUploadException { + return parseRequest(new ServletRequestContext(req)); + } + + /** + * Processes an RFC 1867 + * compliant multipart/form-data stream. + * + * @param ctx The context for the request to be parsed. + * + * @return An iterator to instances of FileItemStream + * parsed from the request, in the order that they were + * transmitted. + * + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @throws IOException An I/O error occurred. This may be a network + * error while communicating with the client or a problem while + * storing the uploaded content. + */ + public FileItemIterator getItemIterator(RequestContext ctx) + throws FileUploadException, IOException { + return new FileItemIteratorImpl(ctx); + } + + /** + * Processes an RFC 1867 + * compliant multipart/form-data stream. + * + * @param ctx The context for the request to be parsed. + * + * @return A list of FileItem instances parsed from the + * request, in the order that they were transmitted. + * + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + */ + public List /* FileItem */ parseRequest(RequestContext ctx) + throws FileUploadException { + try { + FileItemIterator iter = getItemIterator(ctx); + List items = new ArrayList(); + FileItemFactory fac = getFileItemFactory(); + if (fac == null) { + throw new NullPointerException( + "No FileItemFactory has been set."); + } + while (iter.hasNext()) { + FileItemStream item = iter.next(); + FileItem fileItem = fac.createItem(item.getFieldName(), + item.getContentType(), item.isFormField(), + item.getName()); + try { + Streams.copy(item.openStream(), fileItem.getOutputStream(), + true); + } catch (FileUploadIOException e) { + throw (FileUploadException) e.getCause(); + } catch (IOException e) { + throw new IOFileUploadException( + "Processing of " + MULTIPART_FORM_DATA + + " request failed. " + e.getMessage(), e); + } + items.add(fileItem); + } + return items; + } catch (FileUploadIOException e) { + throw (FileUploadException) e.getCause(); + } catch (IOException e) { + throw new FileUploadException(e.getMessage(), e); + } + } + + + // ------------------------------------------------------ Protected methods + + + /** + * Retrieves the boundary from the Content-type header. + * + * @param contentType The value of the content type header from which to + * extract the boundary value. + * + * @return The boundary, as a byte array. + */ + protected byte[] getBoundary(String contentType) { + ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + Map params = parser.parse(contentType, ';'); + String boundaryStr = (String) params.get("boundary"); + + if (boundaryStr == null) { + return null; + } + byte[] boundary; + try { + boundary = boundaryStr.getBytes("ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + boundary = boundaryStr.getBytes(); + } + return boundary; + } + + + /** + * Retrieves the file name from the Content-disposition + * header. + * + * @param headers A Map containing the HTTP request headers. + * + * @return The file name for the current encapsulation. + */ + protected String getFileName(Map /* String, String */ headers) { + String fileName = null; + String cd = getHeader(headers, CONTENT_DISPOSITION); + if (cd != null) { + String cdl = cd.toLowerCase(); + if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) { + ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + Map params = parser.parse(cd, ';'); + if (params.containsKey("filename")) { + fileName = (String) params.get("filename"); + if (fileName != null) { + fileName = fileName.trim(); + } else { + // Even if there is no value, the parameter is present, + // so we return an empty file name rather than no file + // name. + fileName = ""; + } + } + } + } + return fileName; + } + + + /** + * Retrieves the field name from the Content-disposition + * header. + * + * @param headers A Map containing the HTTP request headers. + * + * @return The field name for the current encapsulation. + */ + protected String getFieldName(Map /* String, String */ headers) { + String fieldName = null; + String cd = getHeader(headers, CONTENT_DISPOSITION); + if (cd != null && cd.toLowerCase().startsWith(FORM_DATA)) { + + ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + Map params = parser.parse(cd, ';'); + fieldName = (String) params.get("name"); + if (fieldName != null) { + fieldName = fieldName.trim(); + } + } + return fieldName; + } + + + /** + * Creates a new {@link FileItem} instance. + * + * @param headers A Map containing the HTTP request + * headers. + * @param isFormField Whether or not this item is a form field, as + * opposed to a file. + * + * @return A newly created FileItem instance. + * + * @throws FileUploadException if an error occurs. + * @deprecated This method is no longer used in favour of + * internally created instances of {@link FileItem}. + */ + protected FileItem createItem(Map /* String, String */ headers, + boolean isFormField) + throws FileUploadException { + return getFileItemFactory().createItem(getFieldName(headers), + getHeader(headers, CONTENT_TYPE), + isFormField, + getFileName(headers)); + } + + + /** + *

Parses the header-part and returns as key/value + * pairs. + * + *

If there are multiple headers of the same names, the name + * will map to a comma-separated list containing the values. + * + * @param headerPart The header-part of the current + * encapsulation. + * + * @return A Map containing the parsed HTTP request headers. + */ + protected Map /* String, String */ parseHeaders(String headerPart) { + final int len = headerPart.length(); + Map headers = new HashMap(); + int start = 0; + for (;;) { + int end = parseEndOfLine(headerPart, start); + if (start == end) { + break; + } + String header = headerPart.substring(start, end); + start = end + 2; + while (start < len) { + int nonWs = start; + while (nonWs < len) { + char c = headerPart.charAt(nonWs); + if (c != ' ' && c != '\t') { + break; + } + ++nonWs; + } + if (nonWs == start) { + break; + } + // Continuation line found + end = parseEndOfLine(headerPart, nonWs); + header += " " + headerPart.substring(nonWs, end); + start = end + 2; + } + parseHeaderLine(headers, header); + } + return headers; + } + + /** + * Skips bytes until the end of the current line. + * @param headerPart The headers, which are being parsed. + * @param end Index of the last byte, which has yet been + * processed. + * @return Index of the \r\n sequence, which indicates + * end of line. + */ + private int parseEndOfLine(String headerPart, int end) { + int index = end; + for (;;) { + int offset = headerPart.indexOf('\r', index); + if (offset == -1 || offset + 1 >= headerPart.length()) { + throw new IllegalStateException( + "Expected headers to be terminated by an empty line."); + } + if (headerPart.charAt(offset + 1) == '\n') { + return offset; + } + index = offset + 1; + } + } + + /** + * Reads the next header line. + * @param headers String with all headers. + * @param header Map where to store the current header. + */ + private void parseHeaderLine(Map headers, String header) { + final int colonOffset = header.indexOf(':'); + if (colonOffset == -1) { + // This header line is malformed, skip it. + return; + } + String headerName = header.substring(0, colonOffset) + .trim().toLowerCase(); + String headerValue = + header.substring(header.indexOf(':') + 1).trim(); + if (getHeader(headers, headerName) != null) { + // More that one heder of that name exists, + // append to the list. + headers.put(headerName, + getHeader(headers, headerName) + ',' + + headerValue); + } else { + headers.put(headerName, headerValue); + } + } + + /** + * Returns the header with the specified name from the supplied map. The + * header lookup is case-insensitive. + * + * @param headers A Map containing the HTTP request headers. + * @param name The name of the header to return. + * + * @return The value of specified header, or a comma-separated list if + * there were multiple headers of that name. + */ + protected final String getHeader(Map /* String, String */ headers, + String name) { + return (String) headers.get(name.toLowerCase()); + } + + /** + * The iterator, which is returned by + * {@link FileUploadBase#getItemIterator(RequestContext)}. + */ + private class FileItemIteratorImpl implements FileItemIterator { + /** + * Default implementation of {@link FileItemStream}. + */ + private class FileItemStreamImpl implements FileItemStream { + /** The file items content type. + */ + private final String contentType; + /** The file items field name. + */ + private final String fieldName; + /** The file items file name. + */ + private final String name; + /** Whether the file item is a form field. + */ + private final boolean formField; + /** The file items input stream. + */ + private final InputStream stream; + /** Whether the file item was already opened. + */ + private boolean opened; + + /** + * CReates a new instance. + * @param pName The items file name, or null. + * @param pFieldName The items field name. + * @param pContentType The items content type, or null. + * @param pFormField Whether the item is a form field. + */ + FileItemStreamImpl(String pName, String pFieldName, + String pContentType, boolean pFormField) { + name = pName; + fieldName = pFieldName; + contentType = pContentType; + formField = pFormField; + InputStream istream = multi.newInputStream(); + if (fileSizeMax != -1) { + istream = new LimitedInputStream(istream, fileSizeMax) { + protected void raiseError(long pSizeMax, long pCount) + throws IOException { + FileUploadException e = + new FileSizeLimitExceededException( + "The field " + fieldName + + " exceeds its maximum permitted " + + " size of " + pSizeMax + + " characters.", + pCount, pSizeMax); + throw new FileUploadIOException(e); + } + }; + } + stream = istream; + } + + /** + * Returns the items content type, or null. + * @return Content type, if known, or null. + */ + public String getContentType() { + return contentType; + } + + /** + * Returns the items field name. + * @return Field name. + */ + public String getFieldName() { + return fieldName; + } + + /** + * Returns the items file name. + * @return File name, if known, or null. + */ + public String getName() { + return name; + } + + /** + * Returns, whether this is a form field. + * @return True, if the item is a form field, + * otherwise false. + */ + public boolean isFormField() { + return formField; + } + + /** + * Returns an input stream, which may be used to + * read the items contents. + * @return Opened input stream. + * @throws IOException An I/O error occurred. + */ + public InputStream openStream() throws IOException { + if (opened) { + throw new IllegalStateException( + "The stream was already opened."); + } + if (((Closeable) stream).isClosed()) { + throw new FileItemStream.ItemSkippedException(); + } + return stream; + } + + /** + * Closes the file item. + * @throws IOException An I/O error occurred. + */ + void close() throws IOException { + stream.close(); + } + } + + /** + * The multi part stream to process. + */ + private final MultipartStream multi; + /** + * The notifier, which used for triggering the + * {@link ProgressListener}. + */ + private final MultipartStream.ProgressNotifier notifier; + /** + * The boundary, which separates the various parts. + */ + private final byte[] boundary; + /** + * The item, which we currently process. + */ + private FileItemStreamImpl currentItem; + /** + * The current items field name. + */ + private String currentFieldName; + /** + * Whether we are currently skipping the preamble. + */ + private boolean skipPreamble; + /** + * Whether the current item may still be read. + */ + private boolean itemValid; + /** + * Whether we have seen the end of the file. + */ + private boolean eof; + + /** + * Creates a new instance. + * @param ctx The request context. + * @throws FileUploadException An error occurred while + * parsing the request. + * @throws IOException An I/O error occurred. + */ + FileItemIteratorImpl(RequestContext ctx) + throws FileUploadException, IOException { + if (ctx == null) { + throw new NullPointerException("ctx parameter"); + } + + String contentType = ctx.getContentType(); + if ((null == contentType) + || (!contentType.toLowerCase().startsWith(MULTIPART))) { + throw new InvalidContentTypeException( + "the request doesn't contain a " + + MULTIPART_FORM_DATA + + " or " + + MULTIPART_MIXED + + " stream, content type header is " + + contentType); + } + + InputStream input = ctx.getInputStream(); + + if (sizeMax >= 0) { + int requestSize = ctx.getContentLength(); + if (requestSize == -1) { + input = new LimitedInputStream(input, sizeMax) { + protected void raiseError(long pSizeMax, long pCount) + throws IOException { + FileUploadException ex = + new SizeLimitExceededException( + "the request was rejected because" + + " its size (" + pCount + + ") exceeds the configured maximum" + + " (" + pSizeMax + ")", + pCount, pSizeMax); + throw new FileUploadIOException(ex); + } + }; + } else { + if (sizeMax >= 0 && requestSize > sizeMax) { + throw new SizeLimitExceededException( + "the request was rejected because its size (" + + requestSize + + ") exceeds the configured maximum (" + + sizeMax + ")", + requestSize, sizeMax); + } + } + } + + String charEncoding = headerEncoding; + if (charEncoding == null) { + charEncoding = ctx.getCharacterEncoding(); + } + + boundary = getBoundary(contentType); + if (boundary == null) { + throw new FileUploadException( + "the request was rejected because " + + "no multipart boundary was found"); + } + + notifier = new MultipartStream.ProgressNotifier(listener, + ctx.getContentLength()); + multi = new MultipartStream(input, boundary, notifier); + multi.setHeaderEncoding(charEncoding); + + skipPreamble = true; + findNextItem(); + } + + /** + * Called for finding the nex item, if any. + * @return True, if an next item was found, otherwise false. + * @throws IOException An I/O error occurred. + */ + private boolean findNextItem() throws IOException { + if (eof) { + return false; + } + if (currentItem != null) { + currentItem.close(); + currentItem = null; + } + for (;;) { + boolean nextPart; + if (skipPreamble) { + nextPart = multi.skipPreamble(); + } else { + nextPart = multi.readBoundary(); + } + if (!nextPart) { + if (currentFieldName == null) { + // Outer multipart terminated -> No more data + eof = true; + return false; + } + // Inner multipart terminated -> Return to parsing the outer + multi.setBoundary(boundary); + currentFieldName = null; + continue; + } + Map headers = parseHeaders(multi.readHeaders()); + if (currentFieldName == null) { + // We're parsing the outer multipart + String fieldName = getFieldName(headers); + if (fieldName != null) { + String subContentType + = getHeader(headers, CONTENT_TYPE); + if (subContentType != null + && subContentType.toLowerCase() + .startsWith(MULTIPART_MIXED)) { + currentFieldName = fieldName; + // Multiple files associated with this field name + byte[] subBoundary = getBoundary(subContentType); + multi.setBoundary(subBoundary); + skipPreamble = true; + continue; + } + String fileName = getFileName(headers); + currentItem = new FileItemStreamImpl(fileName, + fieldName, getHeader(headers, CONTENT_TYPE), + fileName == null); + notifier.noteItem(); + itemValid = true; + return true; + } + } else { + String fileName = getFileName(headers); + if (fileName != null) { + currentItem = new FileItemStreamImpl(fileName, + currentFieldName, + getHeader(headers, CONTENT_TYPE), + false); + notifier.noteItem(); + itemValid = true; + return true; + } + } + multi.discardBodyData(); + } + } + + /** + * Returns, whether another instance of {@link FileItemStream} + * is available. + * @throws FileUploadException Parsing or processing the + * file item failed. + * @throws IOException Reading the file item failed. + * @return True, if one or more additional file items + * are available, otherwise false. + */ + public boolean hasNext() throws FileUploadException, IOException { + if (eof) { + return false; + } + if (itemValid) { + return true; + } + return findNextItem(); + } + + /** + * Returns the next available {@link FileItemStream}. + * @throws java.util.NoSuchElementException No more items are + * available. Use {@link #hasNext()} to prevent this exception. + * @throws FileUploadException Parsing or processing the + * file item failed. + * @throws IOException Reading the file item failed. + * @return FileItemStream instance, which provides + * access to the next file item. + */ + public FileItemStream next() throws FileUploadException, IOException { + if (eof || (!itemValid && !hasNext())) { + throw new NoSuchElementException(); + } + itemValid = false; + return currentItem; + } + } + + /** + * This exception is thrown for hiding an inner + * {@link FileUploadException} in an {@link IOException}. + */ + public static class FileUploadIOException extends IOException { + /** The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = -7047616958165584154L; + /** The exceptions cause; we overwrite the parent + * classes field, which is available since Java + * 1.4 only. + */ + private final FileUploadException cause; + + /** + * Creates a FileUploadIOException with the + * given cause. + * @param pCause The exceptions cause, if any, or null. + */ + public FileUploadIOException(FileUploadException pCause) { + // We're not doing super(pCause) cause of 1.3 compatibility. + cause = pCause; + } + + /** + * Returns the exceptions cause. + * @return The exceptions cause, if any, or null. + */ + public Throwable getCause() { + return cause; + } + } + + /** + * Thrown to indicate that the request is not a multipart request. + */ + public static class InvalidContentTypeException + extends FileUploadException { + /** The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = -9073026332015646668L; + + /** + * Constructs a InvalidContentTypeException with no + * detail message. + */ + public InvalidContentTypeException() { + // Nothing to do. + } + + /** + * Constructs an InvalidContentTypeException with + * the specified detail message. + * + * @param message The detail message. + */ + public InvalidContentTypeException(String message) { + super(message); + } + } + + /** + * Thrown to indicate an IOException. + */ + public static class IOFileUploadException extends FileUploadException { + /** The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = 1749796615868477269L; + /** The exceptions cause; we overwrite the parent + * classes field, which is available since Java + * 1.4 only. + */ + private final IOException cause; + + /** + * Creates a new instance with the given cause. + * @param pMsg The detail message. + * @param pException The exceptions cause. + */ + public IOFileUploadException(String pMsg, IOException pException) { + super(pMsg); + cause = pException; + } + + /** + * Returns the exceptions cause. + * @return The exceptions cause, if any, or null. + */ + public Throwable getCause() { + return cause; + } + } + + /** This exception is thrown, if a requests permitted size + * is exceeded. + */ + protected abstract static class SizeException extends FileUploadException { + /** + * The actual size of the request. + */ + private final long actual; + + /** + * The maximum permitted size of the request. + */ + private final long permitted; + + /** + * Creates a new instance. + * @param message The detail message. + * @param actual The actual number of bytes in the request. + * @param permitted The requests size limit, in bytes. + */ + protected SizeException(String message, long actual, long permitted) { + super(message); + this.actual = actual; + this.permitted = permitted; + } + + /** + * Retrieves the actual size of the request. + * + * @return The actual size of the request. + */ + public long getActualSize() { + return actual; + } + + /** + * Retrieves the permitted size of the request. + * + * @return The permitted size of the request. + */ + public long getPermittedSize() { + return permitted; + } + } + + /** + * Thrown to indicate that the request size is not specified. In other + * words, it is thrown, if the content-length header is missing or + * contains the value -1. + * @deprecated As of commons-fileupload 1.2, the presence of a + * content-length header is no longer required. + */ + public static class UnknownSizeException + extends FileUploadException { + /** The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = 7062279004812015273L; + + /** + * Constructs a UnknownSizeException with no + * detail message. + */ + public UnknownSizeException() { + super(); + } + + /** + * Constructs an UnknownSizeException with + * the specified detail message. + * + * @param message The detail message. + */ + public UnknownSizeException(String message) { + super(message); + } + } + + /** + * Thrown to indicate that the request size exceeds the configured maximum. + */ + public static class SizeLimitExceededException + extends SizeException { + /** The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = -2474893167098052828L; + + /** + * @deprecated Replaced by + * {@link #SizeLimitExceededException(String, long, long)} + */ + public SizeLimitExceededException() { + this(null, 0, 0); + } + + /** + * @deprecated Replaced by + * {@link #SizeLimitExceededException(String, long, long)} + * @param message The exceptions detail message. + */ + public SizeLimitExceededException(String message) { + this(message, 0, 0); + } + + /** + * Constructs a SizeExceededException with + * the specified detail message, and actual and permitted sizes. + * + * @param message The detail message. + * @param actual The actual request size. + * @param permitted The maximum permitted request size. + */ + public SizeLimitExceededException(String message, long actual, + long permitted) { + super(message, actual, permitted); + } + } + + /** + * Thrown to indicate that A files size exceeds the configured maximum. + */ + public static class FileSizeLimitExceededException + extends SizeException { + /** The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = 8150776562029630058L; + + /** + * Constructs a SizeExceededException with + * the specified detail message, and actual and permitted sizes. + * + * @param message The detail message. + * @param actual The actual request size. + * @param permitted The maximum permitted request size. + */ + public FileSizeLimitExceededException(String message, long actual, + long permitted) { + super(message, actual, permitted); + } + } + + /** + * Returns the progress listener. + * @return The progress listener, if any, or null. + */ + public ProgressListener getProgressListener() { + return listener; + } + + /** + * Sets the progress listener. + * @param pListener The progress listener, if any. Defaults to null. + */ + public void setProgressListener(ProgressListener pListener) { + listener = pListener; + } +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUploadException.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUploadException.java new file mode 100755 index 000000000..2e5b1b9e4 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/FileUploadException.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + +import java.io.PrintStream; +import java.io.PrintWriter; + + +/** + * Exception for errors encountered while processing the request. + * + * @author John McNally + * @version $Id$ + */ +public class FileUploadException extends Exception { + /** + * Serial version UID, being used, if the exception + * is serialized. + */ + private static final long serialVersionUID = 8881893724388807504L; + /** + * The exceptions cause. We overwrite the cause of + * the super class, which isn't available in Java 1.3. + */ + private final Throwable cause; + + /** + * Constructs a new FileUploadException without message. + */ + public FileUploadException() { + this(null, null); + } + + /** + * Constructs a new FileUploadException with specified detail + * message. + * + * @param msg the error message. + */ + public FileUploadException(final String msg) { + this(msg, null); + } + + /** + * Creates a new FileUploadException with the given + * detail message and cause. + * @param msg The exceptions detail message. + * @param cause The exceptions cause. + */ + public FileUploadException(String msg, Throwable cause) { + super(msg); + this.cause = cause; + } + + /** + * Prints this throwable and its backtrace to the specified print stream. + * + * @param stream PrintStream to use for output + */ + public void printStackTrace(PrintStream stream) { + super.printStackTrace(stream); + if (cause != null) { + stream.println("Caused by:"); + cause.printStackTrace(stream); + } + } + + /** + * Prints this throwable and its backtrace to the specified + * print writer. + * + * @param writer PrintWriter to use for output + */ + public void printStackTrace(PrintWriter writer) { + super.printStackTrace(writer); + if (cause != null) { + writer.println("Caused by:"); + cause.printStackTrace(writer); + } + } +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/MultipartStream.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/MultipartStream.java new file mode 100755 index 000000000..6ea2bbd6e --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/MultipartStream.java @@ -0,0 +1,1047 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import com.fr.third.org.apache.commons.fileupload.util.Closeable; +import com.fr.third.org.apache.commons.fileupload.util.Streams; + +/** + *

Low level API for processing file uploads. + * + *

This class can be used to process data streams conforming to MIME + * 'multipart' format as defined in + * RFC 1867. Arbitrarily + * large amounts of data in the stream can be processed under constant + * memory usage. + * + *

The format of the stream is defined in the following way:
+ * + * + * multipart-body := preamble 1*encapsulation close-delimiter epilogue
+ * encapsulation := delimiter body CRLF
+ * delimiter := "--" boundary CRLF
+ * close-delimiter := "--" boudary "--"
+ * preamble := <ignore>
+ * epilogue := <ignore>
+ * body := header-part CRLF body-part
+ * header-part := 1*header CRLF
+ * header := header-name ":" header-value
+ * header-name := <printable ascii characters except ":">
+ * header-value := <any ascii characters except CR & LF>
+ * body-data := <arbitrary data>
+ *
+ * + *

Note that body-data can contain another mulipart entity. There + * is limited support for single pass processing of such nested + * streams. The nested stream is required to have a + * boundary token of the same length as the parent stream (see {@link + * #setBoundary(byte[])}). + * + *

Here is an example of usage of this class.
+ * + *

+ *    try {
+ *        MultipartStream multipartStream = new MultipartStream(input,
+ *                                                              boundary);
+ *        boolean nextPart = multipartStream.skipPreamble();
+ *        OutputStream output;
+ *        while(nextPart) {
+ *            header = chunks.readHeader();
+ *            // process headers
+ *            // create some output stream
+ *            multipartStream.readBodyPart(output);
+ *            nextPart = multipartStream.readBoundary();
+ *        }
+ *    } catch(MultipartStream.MalformedStreamException e) {
+ *          // the stream failed to follow required syntax
+ *    } catch(IOException) {
+ *          // a read or write error occurred
+ *    }
+ *
+ * 
+ * + * @author Rafal Krzewski + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id$ + */ +public class MultipartStream { + /** + * Internal class, which is used to invoke the + * {@link ProgressListener}. + */ + static class ProgressNotifier { + /** The listener to invoke. + */ + private final ProgressListener listener; + /** Number of expected bytes, if known, or -1. + */ + private final long contentLength; + /** Number of bytes, which have been read so far. + */ + private long bytesRead; + /** Number of items, which have been read so far. + */ + private int items; + /** Creates a new instance with the given listener + * and content length. + * @param pListener The listener to invoke. + * @param pContentLength The expected content length. + */ + ProgressNotifier(ProgressListener pListener, long pContentLength) { + listener = pListener; + contentLength = pContentLength; + } + /** Called to indicate that bytes have been read. + * @param pBytes Number of bytes, which have been read. + */ + void noteBytesRead(int pBytes) { + /* Indicates, that the given number of bytes have been read from + * the input stream. + */ + bytesRead += pBytes; + notifyListener(); + } + /** Called to indicate, that a new file item has been detected. + */ + void noteItem() { + ++items; + } + /** Called for notifying the listener. + */ + private void notifyListener() { + if (listener != null) { + listener.update(bytesRead, contentLength, items); + } + } + } + + // ----------------------------------------------------- Manifest constants + + + /** + * The Carriage Return ASCII character value. + */ + public static final byte CR = 0x0D; + + + /** + * The Line Feed ASCII character value. + */ + public static final byte LF = 0x0A; + + + /** + * The dash (-) ASCII character value. + */ + public static final byte DASH = 0x2D; + + + /** + * The maximum length of header-part that will be + * processed (10 kilobytes = 10240 bytes.). + */ + public static final int HEADER_PART_SIZE_MAX = 10240; + + + /** + * The default length of the buffer used for processing a request. + */ + protected static final int DEFAULT_BUFSIZE = 4096; + + + /** + * A byte sequence that marks the end of header-part + * (CRLFCRLF). + */ + protected static final byte[] HEADER_SEPARATOR = { + CR, LF, CR, LF }; + + + /** + * A byte sequence that that follows a delimiter that will be + * followed by an encapsulation (CRLF). + */ + protected static final byte[] FIELD_SEPARATOR = { + CR, LF}; + + + /** + * A byte sequence that that follows a delimiter of the last + * encapsulation in the stream (--). + */ + protected static final byte[] STREAM_TERMINATOR = { + DASH, DASH}; + + + /** + * A byte sequence that precedes a boundary (CRLF--). + */ + protected static final byte[] BOUNDARY_PREFIX = { + CR, LF, DASH, DASH}; + + + /** + * The number of bytes, over and above the boundary size, to use for the + * keep region. + */ + private static final int KEEP_REGION_PAD = 3; + + + // ----------------------------------------------------------- Data members + + + /** + * The input stream from which data is read. + */ + private final InputStream input; + + + /** + * The length of the boundary token plus the leading CRLF--. + */ + private int boundaryLength; + + + /** + * The amount of data, in bytes, that must be kept in the buffer in order + * to detect delimiters reliably. + */ + private int keepRegion; + + + /** + * The byte sequence that partitions the stream. + */ + private byte[] boundary; + + + /** + * The length of the buffer used for processing the request. + */ + private final int bufSize; + + + /** + * The buffer used for processing the request. + */ + private final byte[] buffer; + + + /** + * The index of first valid character in the buffer. + *
+ * 0 <= head < bufSize + */ + private int head; + + + /** + * The index of last valid characer in the buffer + 1. + *
+ * 0 <= tail <= bufSize + */ + private int tail; + + + /** + * The content encoding to use when reading headers. + */ + private String headerEncoding; + + + /** + * The progress notifier, if any, or null. + */ + private final ProgressNotifier notifier; + + // ----------------------------------------------------------- Constructors + + /** + * Creates a new instance. + * @deprecated Use {@link #MultipartStream(InputStream, byte[], + * com.fr.third.org.apache.commons.fileupload.MultipartStream.ProgressNotifier)}, + * or {@link #MultipartStream(InputStream, byte[], int, + * com.fr.third.org.apache.commons.fileupload.MultipartStream.ProgressNotifier)} + */ + public MultipartStream() { + this(null, null, null); + } + + /** + *

Constructs a MultipartStream with a custom size buffer + * and no progress notifier. + * + *

Note that the buffer must be at least big enough to contain the + * boundary string, plus 4 characters for CR/LF and double dash, plus at + * least one byte of data. Too small a buffer size setting will degrade + * performance. + * + * @param input The InputStream to serve as a data source. + * @param boundary The token used for dividing the stream into + * encapsulations. + * @param bufSize The size of the buffer to be used, in bytes. + * + * @see #MultipartStream(InputStream, byte[], ProgressNotifier) + * @deprecated Use {@link #MultipartStream(InputStream, byte[], int, + * com.fr.third.org.apache.commons.fileupload.MultipartStream.ProgressNotifier)}. + */ + public MultipartStream(InputStream input, byte[] boundary, int bufSize) { + this(input, boundary, bufSize, null); + } + + /** + *

Constructs a MultipartStream with a custom size buffer. + * + *

Note that the buffer must be at least big enough to contain the + * boundary string, plus 4 characters for CR/LF and double dash, plus at + * least one byte of data. Too small a buffer size setting will degrade + * performance. + * + * @param input The InputStream to serve as a data source. + * @param boundary The token used for dividing the stream into + * encapsulations. + * @param bufSize The size of the buffer to be used, in bytes. + * @param pNotifier The notifier, which is used for calling the + * progress listener, if any. + * + * @see #MultipartStream(InputStream, byte[], ProgressNotifier) + */ + MultipartStream(InputStream input, + byte[] boundary, + int bufSize, + ProgressNotifier pNotifier) { + this.input = input; + this.bufSize = bufSize; + this.buffer = new byte[bufSize]; + this.notifier = pNotifier; + + // We prepend CR/LF to the boundary to chop trailng CR/LF from + // body-data tokens. + this.boundary = new byte[boundary.length + BOUNDARY_PREFIX.length]; + this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length; + this.keepRegion = boundary.length + KEEP_REGION_PAD; + System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, + BOUNDARY_PREFIX.length); + System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, + boundary.length); + + head = 0; + tail = 0; + } + + + /** + *

Constructs a MultipartStream with a default size buffer. + * + * @param input The InputStream to serve as a data source. + * @param boundary The token used for dividing the stream into + * encapsulations. + * @param pNotifier An object for calling the progress listener, if any. + * + * + * @see #MultipartStream(InputStream, byte[], int, ProgressNotifier) + */ + MultipartStream(InputStream input, + byte[] boundary, + ProgressNotifier pNotifier) { + this(input, boundary, DEFAULT_BUFSIZE, pNotifier); + } + + /** + *

Constructs a MultipartStream with a default size buffer. + * + * @param input The InputStream to serve as a data source. + * @param boundary The token used for dividing the stream into + * encapsulations. + * + * @deprecated Use {@link #MultipartStream(InputStream, byte[], + * ProgressNotifier)}. + * @see #MultipartStream(InputStream, byte[], int, ProgressNotifier) + */ + public MultipartStream(InputStream input, + byte[] boundary) { + this(input, boundary, DEFAULT_BUFSIZE, null); + } + + // --------------------------------------------------------- Public methods + + + /** + * Retrieves the character encoding used when reading the headers of an + * individual part. When not specified, or null, the platform + * default encoding is used. + + * + * @return The encoding used to read part headers. + */ + public String getHeaderEncoding() { + return headerEncoding; + } + + + /** + * Specifies the character encoding to be used when reading the headers of + * individual parts. When not specified, or null, the platform + * default encoding is used. + * + * @param encoding The encoding used to read part headers. + */ + public void setHeaderEncoding(String encoding) { + headerEncoding = encoding; + } + + + /** + * Reads a byte from the buffer, and refills it as + * necessary. + * + * @return The next byte from the input stream. + * + * @throws IOException if there is no more data available. + */ + public byte readByte() + throws IOException { + // Buffer depleted ? + if (head == tail) { + head = 0; + // Refill. + tail = input.read(buffer, head, bufSize); + if (tail == -1) { + // No more data available. + throw new IOException("No more data is available"); + } + notifier.noteBytesRead(tail); + } + return buffer[head++]; + } + + + /** + * Skips a boundary token, and checks whether more + * encapsulations are contained in the stream. + * + * @return true if there are more encapsulations in + * this stream; false otherwise. + * + * @throws MalformedStreamException if the stream ends unexpecetedly or + * fails to follow required syntax. + */ + public boolean readBoundary() + throws MalformedStreamException { + byte[] marker = new byte[2]; + boolean nextChunk = false; + + head += boundaryLength; + try { + marker[0] = readByte(); + if (marker[0] == LF) { + // Work around IE5 Mac bug with input type=image. + // Because the boundary delimiter, not including the trailing + // CRLF, must not appear within any file (RFC 2046, section + // 5.1.1), we know the missing CR is due to a buggy browser + // rather than a file containing something similar to a + // boundary. + return true; + } + + marker[1] = readByte(); + if (arrayequals(marker, STREAM_TERMINATOR, 2)) { + nextChunk = false; + } else if (arrayequals(marker, FIELD_SEPARATOR, 2)) { + nextChunk = true; + } else { + throw new MalformedStreamException( + "Unexpected characters follow a boundary"); + } + } catch (IOException e) { + throw new MalformedStreamException("Stream ended unexpectedly"); + } + return nextChunk; + } + + + /** + *

Changes the boundary token used for partitioning the stream. + * + *

This method allows single pass processing of nested multipart + * streams. + * + *

The boundary token of the nested stream is required + * to be of the same length as the boundary token in parent stream. + * + *

Restoring the parent stream boundary token after processing of a + * nested stream is left to the application. + * + * @param boundary The boundary to be used for parsing of the nested + * stream. + * + * @throws IllegalBoundaryException if the boundary + * has a different length than the one + * being currently parsed. + */ + public void setBoundary(byte[] boundary) + throws IllegalBoundaryException { + if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) { + throw new IllegalBoundaryException( + "The length of a boundary token can not be changed"); + } + System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, + boundary.length); + } + + + /** + *

Reads the header-part of the current + * encapsulation. + * + *

Headers are returned verbatim to the input stream, including the + * trailing CRLF marker. Parsing is left to the + * application. + * + *

TODO allow limiting maximum header size to + * protect against abuse. + * + * @return The header-part of the current encapsulation. + * + * @throws MalformedStreamException if the stream ends unexpecetedly. + */ + public String readHeaders() + throws MalformedStreamException { + int i = 0; + byte[] b = new byte[1]; + // to support multi-byte characters + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int sizeMax = HEADER_PART_SIZE_MAX; + int size = 0; + while (i < HEADER_SEPARATOR.length) { + try { + b[0] = readByte(); + } catch (IOException e) { + throw new MalformedStreamException("Stream ended unexpectedly"); + } + size++; + if (b[0] == HEADER_SEPARATOR[i]) { + i++; + } else { + i = 0; + } + if (size <= sizeMax) { + baos.write(b[0]); + } + } + + String headers = null; + if (headerEncoding != null) { + try { + headers = baos.toString(headerEncoding); + } catch (UnsupportedEncodingException e) { + // Fall back to platform default if specified encoding is not + // supported. + headers = baos.toString(); + } + } else { + headers = baos.toString(); + } + + return headers; + } + + + /** + *

Reads body-data from the current + * encapsulation and writes its contents into the + * output Stream. + * + *

Arbitrary large amounts of data can be processed by this + * method using a constant size buffer. (see {@link + * #MultipartStream(InputStream,byte[],int, ProgressNotifier) constructor}). + * + * @param output The Stream to write data into. May + * be null, in which case this method is equivalent + * to {@link #discardBodyData()}. + * + * @return the amount of data written. + * + * @throws MalformedStreamException if the stream ends unexpectedly. + * @throws IOException if an i/o error occurs. + */ + public int readBodyData(OutputStream output) + throws MalformedStreamException, IOException { + final InputStream istream = newInputStream(); + return (int) Streams.copy(istream, output, false); + } + + /** + * Creates a new {@link ItemInputStream}. + * @return A new instance of {@link ItemInputStream}. + */ + ItemInputStream newInputStream() { + return new ItemInputStream(); + } + + /** + *

Reads body-data from the current + * encapsulation and discards it. + * + *

Use this method to skip encapsulations you don't need or don't + * understand. + * + * @return The amount of data discarded. + * + * @throws MalformedStreamException if the stream ends unexpectedly. + * @throws IOException if an i/o error occurs. + */ + public int discardBodyData() + throws MalformedStreamException, + IOException { + return readBodyData(null); + } + + + /** + * Finds the beginning of the first encapsulation. + * + * @return true if an encapsulation was found in + * the stream. + * + * @throws IOException if an i/o error occurs. + */ + public boolean skipPreamble() + throws IOException { + // First delimiter may be not preceeded with a CRLF. + System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2); + boundaryLength = boundary.length - 2; + try { + // Discard all data up to the delimiter. + discardBodyData(); + + // Read boundary - if succeded, the stream contains an + // encapsulation. + return readBoundary(); + } catch (MalformedStreamException e) { + return false; + } finally { + // Restore delimiter. + System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2); + boundaryLength = boundary.length; + boundary[0] = CR; + boundary[1] = LF; + } + } + + + /** + * Compares count first bytes in the arrays + * a and b. + * + * @param a The first array to compare. + * @param b The second array to compare. + * @param count How many bytes should be compared. + * + * @return true if count first bytes in arrays + * a and b are equal. + */ + public static boolean arrayequals(byte[] a, + byte[] b, + int count) { + for (int i = 0; i < count; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + + /** + * Searches for a byte of specified value in the buffer, + * starting at the specified position. + * + * @param value The value to find. + * @param pos The starting position for searching. + * + * @return The position of byte found, counting from beginning of the + * buffer, or -1 if not found. + */ + protected int findByte(byte value, + int pos) { + for (int i = pos; i < tail; i++) { + if (buffer[i] == value) { + return i; + } + } + + return -1; + } + + + /** + * Searches for the boundary in the buffer + * region delimited by head and tail. + * + * @return The position of the boundary found, counting from the + * beginning of the buffer, or -1 if + * not found. + */ + protected int findSeparator() { + int first; + int match = 0; + int maxpos = tail - boundaryLength; + for (first = head; + (first <= maxpos) && (match != boundaryLength); + first++) { + first = findByte(boundary[0], first); + if (first == -1 || (first > maxpos)) { + return -1; + } + for (match = 1; match < boundaryLength; match++) { + if (buffer[first + match] != boundary[match]) { + break; + } + } + } + if (match == boundaryLength) { + return first - 1; + } + return -1; + } + + /** + * Thrown to indicate that the input stream fails to follow the + * required syntax. + */ + public static class MalformedStreamException + extends IOException { + /** + * Constructs a MalformedStreamException with no + * detail message. + */ + public MalformedStreamException() { + super(); + } + + /** + * Constructs an MalformedStreamException with + * the specified detail message. + * + * @param message The detail message. + */ + public MalformedStreamException(String message) { + super(message); + } + } + + + /** + * Thrown upon attempt of setting an invalid boundary token. + */ + public static class IllegalBoundaryException + extends IOException { + /** + * Constructs an IllegalBoundaryException with no + * detail message. + */ + public IllegalBoundaryException() { + super(); + } + + /** + * Constructs an IllegalBoundaryException with + * the specified detail message. + * + * @param message The detail message. + */ + public IllegalBoundaryException(String message) { + super(message); + } + } + + /** + * An {@link InputStream} for reading an items contents. + */ + public class ItemInputStream extends InputStream implements Closeable { + /** The number of bytes, which have been read so far. + */ + private long total; + /** The number of bytes, which must be hold, because + * they might be a part of the boundary. + */ + private int pad; + /** The current offset in the buffer. + */ + private int pos; + /** Whether the stream is already closed. + */ + private boolean closed; + + /** + * Creates a new instance. + */ + ItemInputStream() { + findSeparator(); + } + + /** + * Called for finding the separator. + */ + private void findSeparator() { + pos = MultipartStream.this.findSeparator(); + if (pos == -1) { + if (tail - head > keepRegion) { + pad = keepRegion; + } else { + pad = tail - head; + } + } + } + + /** + * Returns the number of bytes, which have been read + * by the stream. + * @return Number of bytes, which have been read so far. + */ + public long getBytesRead() { + return total; + } + + /** + * Returns the number of bytes, which are currently + * available, without blocking. + * @throws IOException An I/O error occurs. + * @return Number of bytes in the buffer. + */ + public int available() throws IOException { + if (pos == -1) { + return tail - head - pad; + } + return pos - head; + } + + /** Offset when converting negative bytes to integers. + */ + private static final int BYTE_POSITIVE_OFFSET = 256; + + /** + * Returns the next byte in the stream. + * @return The next byte in the stream, as a non-negative + * integer, or -1 for EOF. + * @throws IOException An I/O error occurred. + */ + public int read() throws IOException { + if (closed) { + throw new FileItemStream.ItemSkippedException(); + } + if (available() == 0) { + if (makeAvailable() == 0) { + return -1; + } + } + ++total; + int b = buffer[head++]; + if (b >= 0) { + return b; + } + return b + BYTE_POSITIVE_OFFSET; + } + + /** + * Reads bytes into the given buffer. + * @param b The destination buffer, where to write to. + * @param off Offset of the first byte in the buffer. + * @param len Maximum number of bytes to read. + * @return Number of bytes, which have been actually read, + * or -1 for EOF. + * @throws IOException An I/O error occurred. + */ + public int read(byte[] b, int off, int len) throws IOException { + if (closed) { + throw new FileItemStream.ItemSkippedException(); + } + if (len == 0) { + return 0; + } + int res = available(); + if (res == 0) { + res = makeAvailable(); + if (res == 0) { + return -1; + } + } + res = Math.min(res, len); + System.arraycopy(buffer, head, b, off, res); + head += res; + total += res; + return res; + } + + /** + * Closes the input stream. + * @throws IOException An I/O error occurred. + */ + public void close() throws IOException { + if (closed) { + return; + } + for (;;) { + int av = available(); + if (av == 0) { + av = makeAvailable(); + if (av == 0) { + break; + } + } + skip(av); + } + closed = true; + } + + /** + * Skips the given number of bytes. + * @param bytes Number of bytes to skip. + * @return The number of bytes, which have actually been + * skipped. + * @throws IOException An I/O error occurred. + */ + public long skip(long bytes) throws IOException { + if (closed) { + throw new FileItemStream.ItemSkippedException(); + } + int av = available(); + if (av == 0) { + av = makeAvailable(); + if (av == 0) { + return 0; + } + } + long res = Math.min(av, bytes); + head += res; + return res; + } + + /** + * Attempts to read more data. + * @return Number of available bytes + * @throws IOException An I/O error occurred. + */ + private int makeAvailable() throws IOException { + if (pos != -1) { + return 0; + } + + // Move the data to the beginning of the buffer. + total += tail - head - pad; + System.arraycopy(buffer, tail - pad, buffer, 0, pad); + + // Refill buffer with new data. + head = 0; + int bytesRead = input.read(buffer, pad, bufSize - pad); + if (bytesRead == -1) { + // The last pad amount is left in the buffer. + // Boundary can't be in there so signal an error + // condition. + throw new MalformedStreamException( + "Stream ended unexpectedly"); + } + notifier.noteBytesRead(bytesRead); + tail = pad + bytesRead; + findSeparator(); + return available(); + } + + /** + * Returns, whether the stream is closed. + * @return True, if the stream is closed, otherwise false. + */ + public boolean isClosed() { + return closed; + } + } + + // ------------------------------------------------------ Debugging methods + + + // These are the methods that were used to debug this stuff. + /* + + // Dump data. + protected void dump() + { + System.out.println("01234567890"); + byte[] temp = new byte[buffer.length]; + for(int i=0; i i1) { + result = new String(chars, i1, i2 - i1); + } + return result; + } + + /** + * Tests if the given character is present in the array of characters. + * + * @param ch the character to test for presense in the array of characters + * @param charray the array of characters to test against + * + * @return true if the character is present in the array of + * characters, false otherwise. + */ + private boolean isOneOf(char ch, final char[] charray) { + boolean result = false; + for (int i = 0; i < charray.length; i++) { + if (ch == charray[i]) { + result = true; + break; + } + } + return result; + } + + /** + * Parses out a token until any of the given terminators + * is encountered. + * + * @param terminators the array of terminating characters. Any of these + * characters when encountered signify the end of the token + * + * @return the token + */ + private String parseToken(final char[] terminators) { + char ch; + i1 = pos; + i2 = pos; + while (hasChar()) { + ch = chars[pos]; + if (isOneOf(ch, terminators)) { + break; + } + i2++; + pos++; + } + return getToken(false); + } + + /** + * Parses out a token until any of the given terminators + * is encountered outside the quotation marks. + * + * @param terminators the array of terminating characters. Any of these + * characters when encountered outside the quotation marks signify the end + * of the token + * + * @return the token + */ + private String parseQuotedToken(final char[] terminators) { + char ch; + i1 = pos; + i2 = pos; + boolean quoted = false; + boolean charEscaped = false; + while (hasChar()) { + ch = chars[pos]; + if (!quoted && isOneOf(ch, terminators)) { + break; + } + if (!charEscaped && ch == '"') { + quoted = !quoted; + } + charEscaped = (!charEscaped && ch == '\\'); + i2++; + pos++; + + } + return getToken(true); + } + + /** + * Returns true if parameter names are to be converted to lower + * case when name/value pairs are parsed. + * + * @return true if parameter names are to be + * converted to lower case when name/value pairs are parsed. + * Otherwise returns false + */ + public boolean isLowerCaseNames() { + return this.lowerCaseNames; + } + + /** + * Sets the flag if parameter names are to be converted to lower case when + * name/value pairs are parsed. + * + * @param b true if parameter names are to be + * converted to lower case when name/value pairs are parsed. + * false otherwise. + */ + public void setLowerCaseNames(boolean b) { + this.lowerCaseNames = b; + } + + /** + * Extracts a map of name/value pairs from the given string. Names are + * expected to be unique. + * + * @param str the string that contains a sequence of name/value pairs + * @param separator the name/value pairs separator + * + * @return a map of name/value pairs + */ + public Map parse(final String str, char separator) { + if (str == null) { + return new HashMap(); + } + return parse(str.toCharArray(), separator); + } + + /** + * Extracts a map of name/value pairs from the given array of + * characters. Names are expected to be unique. + * + * @param chars the array of characters that contains a sequence of + * name/value pairs + * @param separator the name/value pairs separator + * + * @return a map of name/value pairs + */ + public Map parse(final char[] chars, char separator) { + if (chars == null) { + return new HashMap(); + } + return parse(chars, 0, chars.length, separator); + } + + /** + * Extracts a map of name/value pairs from the given array of + * characters. Names are expected to be unique. + * + * @param chars the array of characters that contains a sequence of + * name/value pairs + * @param offset - the initial offset. + * @param length - the length. + * @param separator the name/value pairs separator + * + * @return a map of name/value pairs + */ + public Map parse( + final char[] chars, + int offset, + int length, + char separator) { + + if (chars == null) { + return new HashMap(); + } + HashMap params = new HashMap(); + this.chars = chars; + this.pos = offset; + this.len = length; + + String paramName = null; + String paramValue = null; + while (hasChar()) { + paramName = parseToken(new char[] { + '=', separator }); + paramValue = null; + if (hasChar() && (chars[pos] == '=')) { + pos++; // skip '=' + paramValue = parseQuotedToken(new char[] { + separator }); + } + if (hasChar() && (chars[pos] == separator)) { + pos++; // skip separator + } + if ((paramName != null) && (paramName.length() > 0)) { + if (this.lowerCaseNames) { + paramName = paramName.toLowerCase(); + } + params.put(paramName, paramValue); + } + } + return params; + } +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/ProgressListener.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/ProgressListener.java new file mode 100755 index 000000000..90501427e --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/ProgressListener.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + + +/** + * The {@link ProgressListener} may be used to display a progress bar + * or do stuff like that. + */ +public interface ProgressListener { + /** Updates the listeners status information. + * @param pBytesRead The total number of bytes, which have been read + * so far. + * @param pContentLength The total number of bytes, which are being + * read. May be -1, if this number is unknown. + * @param pItems The number of the field, which is currently being + * read. (0 = no item so far, 1 = first item is being read, ...) + */ + void update(long pBytesRead, long pContentLength, int pItems); +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/RequestContext.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/RequestContext.java new file mode 100755 index 000000000..6056e7390 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/RequestContext.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload; + +import java.io.InputStream; +import java.io.IOException; + +/** + *

Abstracts access to the request information needed for file uploads. This + * interfsace should be implemented for each type of request that may be + * handled by FileUpload, such as servlets and portlets.

+ * + * @author Martin Cooper + * + * @since FileUpload 1.1 + * + * @version $Id$ + */ +public interface RequestContext { + + /** + * Retrieve the character encoding for the request. + * + * @return The character encoding for the request. + */ + String getCharacterEncoding(); + + /** + * Retrieve the content type of the request. + * + * @return The content type of the request. + */ + String getContentType(); + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + */ + int getContentLength(); + + /** + * Retrieve the input stream for the request. + * + * @return The input stream for the request. + * + * @throws IOException if a problem occurs. + */ + InputStream getInputStream() throws IOException; +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItem.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItem.java new file mode 100755 index 000000000..c5209e255 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItem.java @@ -0,0 +1,702 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload.disk; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.UnsupportedEncodingException; +import java.util.Map; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.FileCleaner; +import org.apache.commons.io.output.DeferredFileOutputStream; + +import com.fr.third.org.apache.commons.fileupload.FileItem; +import com.fr.third.org.apache.commons.fileupload.FileUploadException; +import com.fr.third.org.apache.commons.fileupload.ParameterParser; + + +/** + *

The default implementation of the + * {@link com.fr.third.org.apache.commons.fileupload.FileItem FileItem} interface. + * + *

After retrieving an instance of this class from a {@link + * com.fr.third.org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see + * {@link com.fr.third.org.apache.commons.fileupload.DiskFileUpload + * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may + * either request all contents of file at once using {@link #get()} or + * request an {@link java.io.InputStream InputStream} with + * {@link #getInputStream()} and process the file without attempting to load + * it into memory, which may come handy with large files. + * + *

When using the DiskFileItemFactory, then you should + * consider the following: Temporary files are automatically deleted as + * soon as they are no longer needed. (More precisely, when the + * corresponding instance of {@link java.io.File} is garbage collected.) + * This is done by the so-called reaper thread, which is started + * automatically when the class {@link FileCleaner} is loaded. + * It might make sense to terminate that thread, for example, if + * your web application ends. See the section on "Resource cleanup" + * in the users guide of commons-fileupload.

+ * + * @author Rafal Krzewski + * @author Sean Legassick + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @since FileUpload 1.1 + * + * @version $Id$ + */ +public class DiskFileItem + implements FileItem { + + // ----------------------------------------------------- Manifest constants + + + /** + * Default content charset to be used when no explicit charset + * parameter is provided by the sender. Media subtypes of the + * "text" type are defined to have a default charset value of + * "ISO-8859-1" when received via HTTP. + */ + public static final String DEFAULT_CHARSET = "ISO-8859-1"; + + + // ----------------------------------------------------------- Data members + + + /** + * UID used in unique file name generation. + */ + private static final String UID = + new java.rmi.server.UID().toString() + .replace(':', '_').replace('-', '_'); + + /** + * Counter used in unique identifier generation. + */ + private static int counter = 0; + + + /** + * The name of the form field as provided by the browser. + */ + private String fieldName; + + + /** + * The content type passed by the browser, or null if + * not defined. + */ + private String contentType; + + + /** + * Whether or not this item is a simple form field. + */ + private boolean isFormField; + + + /** + * The original filename in the user's filesystem. + */ + private String fileName; + + + /** + * The size of the item, in bytes. This is used to cache the size when a + * file item is moved from its original location. + */ + private long size = -1; + + + /** + * The threshold above which uploads will be stored on disk. + */ + private int sizeThreshold; + + + /** + * The directory in which uploaded files will be stored, if stored on disk. + */ + private File repository; + + + /** + * Cached contents of the file. + */ + private byte[] cachedContent; + + + /** + * Output stream for this item. + */ + private transient DeferredFileOutputStream dfos; + + /** + * File to allow for serialization of the content of this item. + */ + private File dfosFile; + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs a new DiskFileItem instance. + * + * @param fieldName The name of the form field. + * @param contentType The content type passed by the browser or + * null if not specified. + * @param isFormField Whether or not this item is a plain form field, as + * opposed to a file upload. + * @param fileName The original filename in the user's filesystem, or + * null if not specified. + * @param sizeThreshold The threshold, in bytes, below which items will be + * retained in memory and above which they will be + * stored as a file. + * @param repository The data repository, which is the directory in + * which files will be created, should the item size + * exceed the threshold. + */ + public DiskFileItem(String fieldName, String contentType, + boolean isFormField, String fileName, int sizeThreshold, + File repository) { + this.fieldName = fieldName; + this.contentType = contentType; + this.isFormField = isFormField; + this.fileName = fileName; + this.sizeThreshold = sizeThreshold; + this.repository = repository; + } + + + // ------------------------------- Methods from javax.activation.DataSource + + + /** + * Returns an {@link java.io.InputStream InputStream} that can be + * used to retrieve the contents of the file. + * + * @return An {@link java.io.InputStream InputStream} that can be + * used to retrieve the contents of the file. + * + * @throws IOException if an error occurs. + */ + public InputStream getInputStream() + throws IOException { + if (!isInMemory()) { + return new FileInputStream(dfos.getFile()); + } + + if (cachedContent == null) { + cachedContent = dfos.getData(); + } + return new ByteArrayInputStream(cachedContent); + } + + + /** + * Returns the content type passed by the agent or null if + * not defined. + * + * @return The content type passed by the agent or null if + * not defined. + */ + public String getContentType() { + return contentType; + } + + + /** + * Returns the content charset passed by the agent or null if + * not defined. + * + * @return The content charset passed by the agent or null if + * not defined. + */ + public String getCharSet() { + ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + Map params = parser.parse(getContentType(), ';'); + return (String) params.get("charset"); + } + + + /** + * Returns the original filename in the client's filesystem. + * + * @return The original filename in the client's filesystem. + */ + public String getName() { + return fileName; + } + + + // ------------------------------------------------------- FileItem methods + + + /** + * Provides a hint as to whether or not the file contents will be read + * from memory. + * + * @return true if the file contents will be read + * from memory; false otherwise. + */ + public boolean isInMemory() { + if (cachedContent != null) { + return true; + } + return dfos.isInMemory(); + } + + + /** + * Returns the size of the file. + * + * @return The size of the file, in bytes. + */ + public long getSize() { + if (size >= 0) { + return size; + } else if (cachedContent != null) { + return cachedContent.length; + } else if (dfos.isInMemory()) { + return dfos.getData().length; + } else { + return dfos.getFile().length(); + } + } + + + /** + * Returns the contents of the file as an array of bytes. If the + * contents of the file were not yet cached in memory, they will be + * loaded from the disk storage and cached. + * + * @return The contents of the file as an array of bytes. + */ + public byte[] get() { + if (isInMemory()) { + if (cachedContent == null) { + cachedContent = dfos.getData(); + } + return cachedContent; + } + + byte[] fileData = new byte[(int) getSize()]; + FileInputStream fis = null; + + try { + fis = new FileInputStream(dfos.getFile()); + fis.read(fileData); + } catch (IOException e) { + fileData = null; + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + // ignore + } + } + } + + return fileData; + } + + + /** + * Returns the contents of the file as a String, using the specified + * encoding. This method uses {@link #get()} to retrieve the + * contents of the file. + * + * @param charset The charset to use. + * + * @return The contents of the file, as a string. + * + * @throws UnsupportedEncodingException if the requested character + * encoding is not available. + */ + public String getString(final String charset) + throws UnsupportedEncodingException { + return new String(get(), charset); + } + + + /** + * Returns the contents of the file as a String, using the default + * character encoding. This method uses {@link #get()} to retrieve the + * contents of the file. + * + * @return The contents of the file, as a string. + * + * @todo Consider making this method throw UnsupportedEncodingException. + */ + public String getString() { + byte[] rawdata = get(); + String charset = getCharSet(); + if (charset == null) { + charset = DEFAULT_CHARSET; + } + try { + return new String(rawdata, charset); + } catch (UnsupportedEncodingException e) { + return new String(rawdata); + } + } + + + /** + * A convenience method to write an uploaded item to disk. The client code + * is not concerned with whether or not the item is stored in memory, or on + * disk in a temporary location. They just want to write the uploaded item + * to a file. + *

+ * This implementation first attempts to rename the uploaded item to the + * specified destination file, if the item was originally written to disk. + * Otherwise, the data will be copied to the specified file. + *

+ * This method is only guaranteed to work once, the first time it + * is invoked for a particular item. This is because, in the event that the + * method renames a temporary file, that file will no longer be available + * to copy or rename again at a later time. + * + * @param file The File into which the uploaded item should + * be stored. + * + * @throws Exception if an error occurs. + */ + public void write(File file) throws Exception { + if (isInMemory()) { + FileOutputStream fout = null; + try { + fout = new FileOutputStream(file); + fout.write(get()); + } finally { + if (fout != null) { + fout.close(); + } + } + } else { + File outputFile = getStoreLocation(); + if (outputFile != null) { + // Save the length of the file + size = outputFile.length(); + /* + * The uploaded file is being stored on disk + * in a temporary location so move it to the + * desired file. + */ + if (!outputFile.renameTo(file)) { + BufferedInputStream in = null; + BufferedOutputStream out = null; + try { + in = new BufferedInputStream( + new FileInputStream(outputFile)); + out = new BufferedOutputStream( + new FileOutputStream(file)); + IOUtils.copy(in, out); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // ignore + } + } + } + } + } else { + /* + * For whatever reason we cannot write the + * file to disk. + */ + throw new FileUploadException( + "Cannot write uploaded file to disk!"); + } + } + } + + + /** + * Deletes the underlying storage for a file item, including deleting any + * associated temporary disk file. Although this storage will be deleted + * automatically when the FileItem instance is garbage + * collected, this method can be used to ensure that this is done at an + * earlier time, thus preserving system resources. + */ + public void delete() { + cachedContent = null; + File outputFile = getStoreLocation(); + if (outputFile != null && outputFile.exists()) { + outputFile.delete(); + } + } + + + /** + * Returns the name of the field in the multipart form corresponding to + * this file item. + * + * @return The name of the form field. + * + * @see #setFieldName(java.lang.String) + * + */ + public String getFieldName() { + return fieldName; + } + + + /** + * Sets the field name used to reference this file item. + * + * @param fieldName The name of the form field. + * + * @see #getFieldName() + * + */ + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + + /** + * Determines whether or not a FileItem instance represents + * a simple form field. + * + * @return true if the instance represents a simple form + * field; false if it represents an uploaded file. + * + * @see #setFormField(boolean) + * + */ + public boolean isFormField() { + return isFormField; + } + + + /** + * Specifies whether or not a FileItem instance represents + * a simple form field. + * + * @param state true if the instance represents a simple form + * field; false if it represents an uploaded file. + * + * @see #isFormField() + * + */ + public void setFormField(boolean state) { + isFormField = state; + } + + + /** + * Returns an {@link java.io.OutputStream OutputStream} that can + * be used for storing the contents of the file. + * + * @return An {@link java.io.OutputStream OutputStream} that can be used + * for storing the contensts of the file. + * + * @throws IOException if an error occurs. + */ + public OutputStream getOutputStream() + throws IOException { + if (dfos == null) { + File outputFile = getTempFile(); + dfos = new DeferredFileOutputStream(sizeThreshold, outputFile); + } + return dfos; + } + + + // --------------------------------------------------------- Public methods + + + /** + * Returns the {@link java.io.File} object for the FileItem's + * data's temporary location on the disk. Note that for + * FileItems that have their data stored in memory, + * this method will return null. When handling large + * files, you can use {@link java.io.File#renameTo(java.io.File)} to + * move the file to new location without copying the data, if the + * source and destination locations reside within the same logical + * volume. + * + * @return The data file, or null if the data is stored in + * memory. + */ + public File getStoreLocation() { + return dfos.getFile(); + } + + + // ------------------------------------------------------ Protected methods + + + /** + * Removes the file contents from the temporary storage. + */ + protected void finalize() { + File outputFile = dfos.getFile(); + + if (outputFile != null && outputFile.exists()) { + outputFile.delete(); + } + } + + + /** + * Creates and returns a {@link java.io.File File} representing a uniquely + * named temporary file in the configured repository path. The lifetime of + * the file is tied to the lifetime of the FileItem instance; + * the file will be deleted when the instance is garbage collected. + * + * @return The {@link java.io.File File} to be used for temporary storage. + */ + protected File getTempFile() { + File tempDir = repository; + if (tempDir == null) { + tempDir = new File(System.getProperty("java.io.tmpdir")); + } + + String tempFileName = "upload_" + UID + "_" + getUniqueId() + ".tmp"; + + File f = new File(tempDir, tempFileName); + FileCleaner.track(f, this); + return f; + } + + + // -------------------------------------------------------- Private methods + + + /** + * Returns an identifier that is unique within the class loader used to + * load this class, but does not have random-like apearance. + * + * @return A String with the non-random looking instance identifier. + */ + private static String getUniqueId() { + final int limit = 100000000; + int current; + synchronized (DiskFileItem.class) { + current = counter++; + } + String id = Integer.toString(current); + + // If you manage to get more than 100 million of ids, you'll + // start getting ids longer than 8 characters. + if (current < limit) { + id = ("00000000" + id).substring(id.length()); + } + return id; + } + + + + + /** + * Returns a string representation of this object. + * + * @return a string representation of this object. + */ + public String toString() { + return "name=" + this.getName() + + ", StoreLocation=" + + String.valueOf(this.getStoreLocation()) + + ", size=" + + this.getSize() + + "bytes, " + + "isFormField=" + isFormField() + + ", FieldName=" + + this.getFieldName(); + } + + + // -------------------------------------------------- Serialization methods + + + /** + * Writes the state of this object during serialization. + * + * @param out The stream to which the state should be written. + * + * @throws IOException if an error occurs. + */ + private void writeObject(ObjectOutputStream out) throws IOException { + // Read the data + if (dfos.isInMemory()) { + cachedContent = get(); + } else { + cachedContent = null; + dfosFile = dfos.getFile(); + } + + // write out values + out.defaultWriteObject(); + } + + /** + * Reads the state of this object during deserialization. + * + * @param in The stream from which the state should be read. + * + * @throws IOException if an error occurs. + * @throws ClassNotFoundException if class cannot be found. + */ + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException { + // read values + in.defaultReadObject(); + + OutputStream output = getOutputStream(); + if (cachedContent != null) { + output.write(cachedContent); + } else { + FileInputStream input = new FileInputStream(dfosFile); + + IOUtils.copy(input, output); + dfosFile.delete(); + dfosFile = null; + } + output.close(); + + cachedContent = null; + } + +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItemFactory.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItemFactory.java new file mode 100755 index 000000000..34c852451 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItemFactory.java @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload.disk; + +import java.io.File; + +import com.fr.third.org.apache.commons.fileupload.FileItem; +import com.fr.third.org.apache.commons.fileupload.FileItemFactory; + +/** + *

The default {@link com.fr.third.org.apache.commons.fileupload.FileItemFactory} + * implementation. This implementation creates + * {@link com.fr.third.org.apache.commons.fileupload.FileItem} instances which keep their + * content either in memory, for smaller items, or in a temporary file on disk, + * for larger items. The size threshold, above which content will be stored on + * disk, is configurable, as is the directory in which temporary files will be + * created.

+ * + *

If not otherwise configured, the default configuration values are as + * follows: + *

    + *
  • Size threshold is 10KB.
  • + *
  • Repository is the system default temp directory, as returned by + * System.getProperty("java.io.tmpdir").
  • + *
+ *

+ * + *

When using the DiskFileItemFactory, then you should + * consider the following: Temporary files are automatically deleted as + * soon as they are no longer needed. (More precisely, when the + * corresponding instance of {@link java.io.File} is garbage collected.) + * This is done by the so-called reaper thread, which is started + * automatically when the class {@link org.apache.commons.io.FileCleaner} + * is loaded. It might make sense to terminate that thread, for example, + * if your web application ends. See the section on "Resource cleanup" + * in the users guide of commons-fileupload.

+ * + * @author Martin Cooper + * + * @since FileUpload 1.1 + * + * @version $Id$ + */ +public class DiskFileItemFactory implements FileItemFactory { + + // ----------------------------------------------------- Manifest constants + + + /** + * The default threshold above which uploads will be stored on disk. + */ + public static final int DEFAULT_SIZE_THRESHOLD = 10240; + + + // ----------------------------------------------------- Instance Variables + + + /** + * The directory in which uploaded files will be stored, if stored on disk. + */ + private File repository; + + + /** + * The threshold above which uploads will be stored on disk. + */ + private int sizeThreshold = DEFAULT_SIZE_THRESHOLD; + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an unconfigured instance of this class. The resulting factory + * may be configured by calling the appropriate setter methods. + */ + public DiskFileItemFactory() { + // Does nothing. + } + + + /** + * Constructs a preconfigured instance of this class. + * + * @param sizeThreshold The threshold, in bytes, below which items will be + * retained in memory and above which they will be + * stored as a file. + * @param repository The data repository, which is the directory in + * which files will be created, should the item size + * exceed the threshold. + */ + public DiskFileItemFactory(int sizeThreshold, File repository) { + this.sizeThreshold = sizeThreshold; + this.repository = repository; + } + + + // ------------------------------------------------------------- Properties + + + /** + * Returns the directory used to temporarily store files that are larger + * than the configured size threshold. + * + * @return The directory in which temporary files will be located. + * + * @see #setRepository(java.io.File) + * + */ + public File getRepository() { + return repository; + } + + + /** + * Sets the directory used to temporarily store files that are larger + * than the configured size threshold. + * + * @param repository The directory in which temporary files will be located. + * + * @see #getRepository() + * + */ + public void setRepository(File repository) { + this.repository = repository; + } + + + /** + * Returns the size threshold beyond which files are written directly to + * disk. The default value is 10240 bytes. + * + * @return The size threshold, in bytes. + * + * @see #setSizeThreshold(int) + */ + public int getSizeThreshold() { + return sizeThreshold; + } + + + /** + * Sets the size threshold beyond which files are written directly to disk. + * + * @param sizeThreshold The size threshold, in bytes. + * + * @see #getSizeThreshold() + * + */ + public void setSizeThreshold(int sizeThreshold) { + this.sizeThreshold = sizeThreshold; + } + + + // --------------------------------------------------------- Public Methods + + /** + * Create a new {@link com.fr.third.org.apache.commons.fileupload.disk.DiskFileItem} + * instance from the supplied parameters and the local factory + * configuration. + * + * @param fieldName The name of the form field. + * @param contentType The content type of the form field. + * @param isFormField true if this is a plain form field; + * false otherwise. + * @param fileName The name of the uploaded file, if any, as supplied + * by the browser or other client. + * + * @return The newly created file item. + */ + public FileItem createItem( + String fieldName, + String contentType, + boolean isFormField, + String fileName + ) { + return new DiskFileItem(fieldName, contentType, + isFormField, fileName, sizeThreshold, repository); + } + +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/package.html b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/package.html new file mode 100755 index 000000000..6f3ec1b7f --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/package.html @@ -0,0 +1,58 @@ + + + + + Overview of the com.fr.third.org.apache.commons.fileupload.disk component + + +

+ A disk-based implementation of the + {@link com.fr.third.org.apache.commons.fileupload.FileItem FileItem} + interface. This implementation retains smaller items in memory, while + writing larger ones to disk. The threshold between these two is + configurable, as is the location of files that are written to disk. +

+

+ In typical usage, an instance of + {@link com.fr.third.org.apache.commons.fileupload.disk.DiskFileItemFactory DiskFileItemFactory} + would be created, configured, and then passed to a + {@link com.fr.third.org.apache.commons.fileupload.FileUpload FileUpload} + implementation such as + {@link com.fr.third.org.apache.commons.fileupload.servlet.ServletFileUpload ServletFileUpload} + or + {@link com.fr.third.org.apache.commons.fileupload.portlet.PortletFileUpload PortletFileUpload}. +

+

+ The following code fragment demonstrates this usage. +

+
+        DiskFileItemFactory factory = new DiskFileItemFactory();
+        // maximum size that will be stored in memory
+        factory.setSizeThreshold(4096);
+        // the location for saving data that is larger than getSizeThreshold()
+        factory.setRepository(new File("/tmp"));
+
+        ServletFileUpload upload = new ServletFileUpload(factory);
+
+

+ Please see the FileUpload + User Guide + for further details and examples of how to use this package. +

+ + diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/package.html b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/package.html new file mode 100755 index 000000000..e3206501e --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/package.html @@ -0,0 +1,90 @@ + + + + + Overview of the com.fr.third.org.apache.commons.fileupload component + + +

+ A component for handling HTML file uploads as specified by + RFC 1867. + This component provides support for uploads within both servlets (JSR 53) + and portlets (JSR 168). +

+

+ While this package provides the generic functionality for file uploads, + these classes are not typically used directly. Instead, normal usage + involves one of the provided extensions of + {@link com.fr.third.org.apache.commons.fileupload.FileUpload FileUpload} such as + {@link com.fr.third.org.apache.commons.fileupload.servlet.ServletFileUpload ServletFileUpload} + or + {@link com.fr.third.org.apache.commons.fileupload.portlet.PortletFileUpload PortletFileUpload}, + together with a factory for + {@link com.fr.third.org.apache.commons.fileupload.FileItem FileItem} instances, + such as + {@link com.fr.third.org.apache.commons.fileupload.disk.DiskFileItemFactory DiskFileItemFactory}. +

+

+ The following is a brief example of typical usage in a servlet, storing + the uploaded files on disk. +

+
+    public void doPost(HttpServletRequest req, HttpServletResponse res) {
+        DiskFileItemFactory factory = new DiskFileItemFactory();
+        // maximum size that will be stored in memory
+        factory.setSizeThreshold(4096);
+        // the location for saving data that is larger than getSizeThreshold()
+        factory.setRepository(new File("/tmp"));
+
+        ServletFileUpload upload = new ServletFileUpload(factory);
+        // maximum size before a FileUploadException will be thrown
+        upload.setSizeMax(1000000);
+
+        List fileItems = upload.parseRequest(req);
+        // assume we know there are two files. The first file is a small
+        // text file, the second is unknown and is written to a file on
+        // the server
+        Iterator i = fileItems.iterator();
+        String comment = ((FileItem)i.next()).getString();
+        FileItem fi = (FileItem)i.next();
+        // filename on the client
+        String fileName = fi.getName();
+        // save comment and filename to database
+        ...
+        // write the file
+        fi.write(new File("/www/uploads/", fileName));
+    }
+
+

+ In the example above, the first file is loaded into memory as a + String. Before calling the getString method, + the data may have been in memory or on disk depending on its size. The + second file we assume it will be large and therefore never explicitly + load it into memory, though if it is less than 4096 bytes it will be + in memory before it is written to its final location. When writing to + the final location, if the data is larger than the threshold, an attempt + is made to rename the temporary file to the given location. If it cannot + be renamed, it is streamed to the new location. +

+

+ Please see the FileUpload + User Guide + for further details and examples of how to use this package. +

+ + diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/PortletFileUpload.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/PortletFileUpload.java new file mode 100755 index 000000000..d8cddda00 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/PortletFileUpload.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload.portlet; + +import java.io.IOException; +import java.util.List; + +import javax.portlet.ActionRequest; + +import com.fr.third.org.apache.commons.fileupload.FileItemFactory; +import com.fr.third.org.apache.commons.fileupload.FileItemIterator; +import com.fr.third.org.apache.commons.fileupload.FileUpload; +import com.fr.third.org.apache.commons.fileupload.FileUploadBase; +import com.fr.third.org.apache.commons.fileupload.FileUploadException; + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(javax.servlet.http.HttpServletRequest)} to acquire a list + * of {@link com.fr.third.org.apache.commons.fileupload.FileItem FileItems} associated + * with a given HTML widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @since FileUpload 1.1 + * + * @version $Id$ + */ +public class PortletFileUpload extends FileUpload { + + // ---------------------------------------------------------- Class methods + + + /** + * Utility method that determines whether the request contains multipart + * content. + * + * @param request The portlet request to be evaluated. Must be non-null. + * + * @return true if the request is multipart; + * false otherwise. + */ + public static final boolean isMultipartContent(ActionRequest request) { + return FileUploadBase.isMultipartContent( + new PortletRequestContext(request)); + } + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an uninitialised instance of this class. A factory must be + * configured, using setFileItemFactory(), before attempting + * to parse requests. + * + * @see FileUpload#FileUpload(FileItemFactory) + */ + public PortletFileUpload() { + super(); + } + + + /** + * Constructs an instance of this class which uses the supplied factory to + * create FileItem instances. + * + * @see FileUpload#FileUpload() + * @param fileItemFactory The factory to use for creating file items. + */ + public PortletFileUpload(FileItemFactory fileItemFactory) { + super(fileItemFactory); + } + + + // --------------------------------------------------------- Public methods + + + /** + * Processes an RFC 1867 + * compliant multipart/form-data stream. + * + * @param request The portlet request to be parsed. + * + * @return A list of FileItem instances parsed from the + * request, in the order that they were transmitted. + * + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + */ + public List /* FileItem */ parseRequest(ActionRequest request) + throws FileUploadException { + return parseRequest(new PortletRequestContext(request)); + } + + /** + * Processes an RFC 1867 + * compliant multipart/form-data stream. + * + * @param request The portlet request to be parsed. + * + * @return An iterator to instances of FileItemStream + * parsed from the request, in the order that they were + * transmitted. + * + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @throws IOException An I/O error occurred. This may be a network + * error while communicating with the client or a problem while + * storing the uploaded content. + */ + public FileItemIterator getItemIterator(ActionRequest request) + throws FileUploadException, IOException { + return super.getItemIterator(new PortletRequestContext(request)); + } +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/PortletRequestContext.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/PortletRequestContext.java new file mode 100755 index 000000000..d4cfd0a4e --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/PortletRequestContext.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload.portlet; + +import java.io.InputStream; +import java.io.IOException; +import javax.portlet.ActionRequest; +import com.fr.third.org.apache.commons.fileupload.RequestContext; + +/** + *

Provides access to the request information needed for a request made to + * a portlet.

+ * + * @author Martin Cooper + * + * @since FileUpload 1.1 + * + * @version $Id$ + */ +public class PortletRequestContext implements RequestContext { + + // ----------------------------------------------------- Instance Variables + + /** + * The request for which the context is being provided. + */ + private ActionRequest request; + + + // ----------------------------------------------------------- Constructors + + /** + * Construct a context for this request. + * + * @param request The request to which this context applies. + */ + public PortletRequestContext(ActionRequest request) { + this.request = request; + } + + + // --------------------------------------------------------- Public Methods + + /** + * Retrieve the character encoding for the request. + * + * @return The character encoding for the request. + */ + public String getCharacterEncoding() { + return request.getCharacterEncoding(); + } + + /** + * Retrieve the content type of the request. + * + * @return The content type of the request. + */ + public String getContentType() { + return request.getContentType(); + } + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + */ + public int getContentLength() { + return request.getContentLength(); + } + + /** + * Retrieve the input stream for the request. + * + * @return The input stream for the request. + * + * @throws IOException if a problem occurs. + */ + public InputStream getInputStream() throws IOException { + return request.getPortletInputStream(); + } + + /** + * Returns a string representation of this object. + * + * @return a string representation of this object. + */ + public String toString() { + return "ContentLength=" + + this.getContentLength() + + ", ContentType=" + + this.getContentType(); + } + +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/package.html b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/package.html new file mode 100755 index 000000000..887ac3187 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/portlet/package.html @@ -0,0 +1,49 @@ + + + + + Overview of the com.fr.third.org.apache.commons.fileupload.portlet component + + +

+ An implementation of + {@link com.fr.third.org.apache.commons.fileupload.FileUpload FileUpload} + for use in portlets conforming to JSR 168. This implementation requires + only access to the portlet's current ActionRequest instance, + and a suitable + {@link com.fr.third.org.apache.commons.fileupload.FileItemFactory FileItemFactory} + implementation, such as + {@link com.fr.third.org.apache.commons.fileupload.disk.DiskFileItemFactory DiskFileItemFactory}. +

+

+ The following code fragment demonstrates typical usage. +

+
+        DiskFileItemFactory factory = new DiskFileItemFactory();
+        // Configure the factory here, if desired.
+        PortletFileUpload upload = new PortletFileUpload(factory);
+        // Configure the uploader here, if desired.
+        List fileItems = upload.parseRequest(request);
+
+

+ Please see the FileUpload + User Guide + for further details and examples of how to use this package. +

+ + diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java new file mode 100755 index 000000000..331da41ce --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload.servlet; + +import javax.servlet.ServletContextListener; +import javax.servlet.ServletContextEvent; + +import org.apache.commons.io.FileCleaner; + + +/** + * A servlet context listener, which ensures that the + * {@link org.apache.commons.io.FileCleaner FileCleaner's} + * reaper thread is terminated, + * when the web application is destroyed. + */ +public class FileCleanerCleanup implements ServletContextListener { + /** + * Called when the web application is initialized. Does + * nothing. + * @param sce The servlet context (ignored). + */ + public void contextInitialized(ServletContextEvent sce) { + // Does nothing. + } + + /** + * Called when the web application is being destroyed. + * Calls {@link FileCleaner#exitWhenFinished()}. + * @param sce The servlet context (ignored). + */ + public void contextDestroyed(ServletContextEvent sce) { + FileCleaner.exitWhenFinished(); + } +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/ServletFileUpload.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/ServletFileUpload.java new file mode 100755 index 000000000..44e0613b4 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/ServletFileUpload.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload.servlet; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import com.fr.third.org.apache.commons.fileupload.FileItemFactory; +import com.fr.third.org.apache.commons.fileupload.FileItemIterator; +import com.fr.third.org.apache.commons.fileupload.FileUpload; +import com.fr.third.org.apache.commons.fileupload.FileUploadException; + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * com.fr.third.org.apache.commons.fileupload.FileItem}s associated with a given HTML + * widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id$ + */ +public class ServletFileUpload extends FileUpload { + + // ---------------------------------------------------------- Class methods + + + /** + * Utility method that determines whether the request contains multipart + * content. + * + * @param request The servlet request to be evaluated. Must be non-null. + * + * @return true if the request is multipart; + * false otherwise. + */ + public static final boolean isMultipartContent( + HttpServletRequest request) { + if (!"post".equals(request.getMethod().toLowerCase())) { + return false; + } + String contentType = request.getContentType(); + if (contentType == null) { + return false; + } + if (contentType.toLowerCase().startsWith(MULTIPART)) { + return true; + } + return false; + } + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an uninitialised instance of this class. A factory must be + * configured, using setFileItemFactory(), before attempting + * to parse requests. + * + * @see FileUpload#FileUpload(FileItemFactory) + */ + public ServletFileUpload() { + super(); + } + + + /** + * Constructs an instance of this class which uses the supplied factory to + * create FileItem instances. + * + * @see FileUpload#FileUpload() + * @param fileItemFactory The factory to use for creating file items. + */ + public ServletFileUpload(FileItemFactory fileItemFactory) { + super(fileItemFactory); + } + + + // --------------------------------------------------------- Public methods + + + /** + * Processes an RFC 1867 + * compliant multipart/form-data stream. + * + * @param request The servlet request to be parsed. + * + * @return A list of FileItem instances parsed from the + * request, in the order that they were transmitted. + * + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + */ + public List /* FileItem */ parseRequest(HttpServletRequest request) + throws FileUploadException { + return parseRequest(new ServletRequestContext(request)); + } + + + /** + * Processes an RFC 1867 + * compliant multipart/form-data stream. + * + * @param request The servlet request to be parsed. + * + * @return An iterator to instances of FileItemStream + * parsed from the request, in the order that they were + * transmitted. + * + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @throws IOException An I/O error occurred. This may be a network + * error while communicating with the client or a problem while + * storing the uploaded content. + */ + public FileItemIterator getItemIterator(HttpServletRequest request) + throws FileUploadException, IOException { + return super.getItemIterator(new ServletRequestContext(request)); + } +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/ServletRequestContext.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/ServletRequestContext.java new file mode 100755 index 000000000..371049994 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/ServletRequestContext.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload.servlet; + +import java.io.InputStream; +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import com.fr.third.org.apache.commons.fileupload.RequestContext; + +/** + *

Provides access to the request information needed for a request made to + * an HTTP servlet.

+ * + * @author Martin Cooper + * + * @since FileUpload 1.1 + * + * @version $Id$ + */ +public class ServletRequestContext implements RequestContext { + + // ----------------------------------------------------- Instance Variables + + /** + * The request for which the context is being provided. + */ + private HttpServletRequest request; + + + // ----------------------------------------------------------- Constructors + + /** + * Construct a context for this request. + * + * @param request The request to which this context applies. + */ + public ServletRequestContext(HttpServletRequest request) { + this.request = request; + } + + + // --------------------------------------------------------- Public Methods + + /** + * Retrieve the character encoding for the request. + * + * @return The character encoding for the request. + */ + public String getCharacterEncoding() { + return request.getCharacterEncoding(); + } + + /** + * Retrieve the content type of the request. + * + * @return The content type of the request. + */ + public String getContentType() { + return request.getContentType(); + } + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + */ + public int getContentLength() { + return request.getContentLength(); + } + + /** + * Retrieve the input stream for the request. + * + * @return The input stream for the request. + * + * @throws IOException if a problem occurs. + */ + public InputStream getInputStream() throws IOException { + return request.getInputStream(); + } + + /** + * Returns a string representation of this object. + * + * @return a string representation of this object. + */ + public String toString() { + return "ContentLength=" + + this.getContentLength() + + ", ContentType=" + + this.getContentType(); + } +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/package.html b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/package.html new file mode 100755 index 000000000..7efde0508 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/package.html @@ -0,0 +1,49 @@ + + + + + Overview of the com.fr.third.org.apache.commons.fileupload.servlet component + + +

+ An implementation of + {@link com.fr.third.org.apache.commons.fileupload.FileUpload FileUpload} + for use in servlets conforming to JSR 53. This implementation requires + only access to the servlet's current HttpServletRequest + instance, and a suitable + {@link com.fr.third.org.apache.commons.fileupload.FileItemFactory FileItemFactory} + implementation, such as + {@link com.fr.third.org.apache.commons.fileupload.disk.DiskFileItemFactory DiskFileItemFactory}. +

+

+ The following code fragment demonstrates typical usage. +

+
+        DiskFileItemFactory factory = new DiskFileItemFactory();
+        // Configure the factory here, if desired.
+        ServletFileUpload upload = new ServletFileUpload(factory);
+        // Configure the uploader here, if desired.
+        List fileItems = upload.parseRequest(request);
+
+

+ Please see the FileUpload + User Guide + for further details and examples of how to use this package. +

+ + diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/Closeable.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/Closeable.java new file mode 100755 index 000000000..1b1bb86bf --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/Closeable.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload.util; + +import java.io.IOException; + + +/** + * Interface of an object, which may be closed. + */ +public interface Closeable { + /** + * Closes the object. + * @throws IOException An I/O error occurred. + */ + void close() throws IOException; + + /** + * Returns, whether the object is already closed. + * @return True, if the object is closed, otherwise false. + * @throws IOException An I/O error occurred. + */ + boolean isClosed() throws IOException; +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/LimitedInputStream.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/LimitedInputStream.java new file mode 100755 index 000000000..389b432a6 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/LimitedInputStream.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload.util; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + + +/** + * An input stream, which limits its data size. This stream is + * used, if the content length is unknown. + */ +public abstract class LimitedInputStream + extends FilterInputStream implements Closeable { + /** + * The maximum size of an item, in bytes. + */ + private long sizeMax; + /** + * The current number of bytes. + */ + private long count; + /** + * Whether this stream is already closed. + */ + private boolean closed; + + /** + * Creates a new instance. + * @param pIn The input stream, which shall be limited. + * @param pSizeMax The limit; no more than this number of bytes + * shall be returned by the source stream. + */ + public LimitedInputStream(InputStream pIn, long pSizeMax) { + super(pIn); + sizeMax = pSizeMax; + } + + /** + * Called to indicate, that the input streams limit has + * been exceeded. + * @param pSizeMax The input streams limit, in bytes. + * @param pCount The actual number of bytes. + * @throws IOException The called method is expected + * to raise an IOException. + */ + protected abstract void raiseError(long pSizeMax, long pCount) + throws IOException; + + /** Called to check, whether the input streams + * limit is reached. + * @throws IOException The given limit is exceeded. + */ + private void checkLimit() throws IOException { + if (count > sizeMax) { + raiseError(sizeMax, count); + } + } + + /** + * Reads the next byte of data from this input stream. The value + * byte is returned as an int in the range + * 0 to 255. If no byte is available + * because the end of the stream has been reached, the value + * -1 is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + *

+ * This method + * simply performs in.read() and returns the result. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public int read() throws IOException { + int res = super.read(); + if (res != -1) { + count++; + checkLimit(); + } + return res; + } + + /** + * Reads up to len bytes of data from this input stream + * into an array of bytes. If len is not zero, the method + * blocks until some input is available; otherwise, no + * bytes are read and 0 is returned. + *

+ * This method simply performs in.read(b, off, len) + * and returns the result. + * + * @param b the buffer into which the data is read. + * @param off The start offset in the destination array + * b. + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the stream has been reached. + * @exception NullPointerException If b is null. + * @exception IndexOutOfBoundsException If off is negative, + * len is negative, or len is greater than + * b.length - off + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public int read(byte[] b, int off, int len) throws IOException { + int res = super.read(b, off, len); + if (res > 0) { + count += res; + checkLimit(); + } + return res; + } + + /** + * Returns, whether this stream is already closed. + * @return True, if the stream is closed, otherwise false. + * @throws IOException An I/O error occurred. + */ + public boolean isClosed() throws IOException { + return closed; + } + + /** + * Closes this input stream and releases any system resources + * associated with the stream. + * This + * method simply performs in.close(). + * + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public void close() throws IOException { + closed = true; + super.close(); + } +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/Streams.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/Streams.java new file mode 100755 index 000000000..cde4ee7f2 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/Streams.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.fileupload.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + + +/** Utility class for working with streams. + */ +public final class Streams { + /** + * Private constructor, to prevent instantiation. + * This class has only static methods. + */ + private Streams() { + // Does nothing + } + + /** + * Default buffer size for use in + * {@link #copy(InputStream, OutputStream, boolean)}. + */ + private static final int DEFAULT_BUFFER_SIZE = 8192; + + /** + * Copies the contents of the given {@link InputStream} + * to the given {@link OutputStream}. Shortcut for + *

+     *   copy(pInputStream, pOutputStream, new byte[8192]);
+     * 
+ * @param pInputStream The input stream, which is being read. + * It is guaranteed, that {@link InputStream#close()} is called + * on the stream. + * @param pOutputStream The output stream, to which data should + * be written. May be null, in which case the input streams + * contents are simply discarded. + * @param pClose True guarantees, that {@link OutputStream#close()} + * is called on the stream. False indicates, that only + * {@link OutputStream#flush()} should be called finally. + * + * @return Number of bytes, which have been copied. + * @throws IOException An I/O error occurred. + */ + public static long copy(InputStream pInputStream, + OutputStream pOutputStream, boolean pClose) + throws IOException { + return copy(pInputStream, pOutputStream, pClose, + new byte[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copies the contents of the given {@link InputStream} + * to the given {@link OutputStream}. + * @param pIn The input stream, which is being read. + * It is guaranteed, that {@link InputStream#close()} is called + * on the stream. + * @param pOut The output stream, to which data should + * be written. May be null, in which case the input streams + * contents are simply discarded. + * @param pClose True guarantees, that {@link OutputStream#close()} + * is called on the stream. False indicates, that only + * {@link OutputStream#flush()} should be called finally. + * @param pBuffer Temporary buffer, which is to be used for + * copying data. + * @return Number of bytes, which have been copied. + * @throws IOException An I/O error occurred. + */ + public static long copy(InputStream pIn, + OutputStream pOut, boolean pClose, + byte[] pBuffer) + throws IOException { + OutputStream out = pOut; + InputStream in = pIn; + try { + long total = 0; + for (;;) { + int res = in.read(pBuffer); + if (res == -1) { + break; + } + if (res > 0) { + total += res; + if (out != null) { + out.write(pBuffer, 0, res); + } + } + } + if (out != null) { + if (pClose) { + out.close(); + } else { + out.flush(); + } + out = null; + } + in.close(); + in = null; + return total; + } finally { + if (in != null) { + try { + in.close(); + } catch (Throwable t) { + /* Ignore me */ + } + } + if (pClose && out != null) { + try { + out.close(); + } catch (Throwable t) { + /* Ignore me */ + } + } + } + } + + /** + * This convenience method allows to read a + * {@link com.fr.third.org.apache.commons.fileupload.FileItemStream}'s + * content into a string. The platform's default character encoding + * is used for converting bytes into characters. + * @param pStream The input stream to read. + * @see #asString(InputStream, String) + * @return The streams contents, as a string. + * @throws IOException An I/O error occurred. + */ + public static String asString(InputStream pStream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + copy(pStream, baos, true); + return baos.toString(); + } + + /** + * This convenience method allows to read a + * {@link com.fr.third.org.apache.commons.fileupload.FileItemStream}'s + * content into a string, using the given character encoding. + * @param pStream The input stream to read. + * @param pEncoding The character encoding, typically "UTF-8". + * @see #asString(InputStream) + * @return The streams contents, as a string. + * @throws IOException An I/O error occurred. + */ + public static String asString(InputStream pStream, String pEncoding) + throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + copy(pStream, baos, true); + return baos.toString(pEncoding); + } +} diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/package.html b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/package.html new file mode 100755 index 000000000..acb098eb5 --- /dev/null +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/util/package.html @@ -0,0 +1,29 @@ + + + + + Overview of the com.fr.third.org.apache.commons.fileupload.util component + + +

+ This package contains various IO related utility classes + or methods, which are basically reusable and not necessarily + restricted to the scope of a file upload. +

+ + From d6b90aa603bb048e86c8de4333b4e069e7a4f1d0 Mon Sep 17 00:00:00 2001 From: hzzz Date: Tue, 5 Jun 2018 17:06:10 +0800 Subject: [PATCH 02/18] rm .. --- fine-commons-fileupload/.DS_Store | Bin 6148 -> 0 bytes fine-commons-fileupload/.classpath | 8 -- fine-commons-fileupload/.project | 23 ------ .../.settings/org.eclipse.jdt.core.prefs | 74 ------------------ .../.settings/org.eclipse.jdt.ui.prefs | 3 - fine-commons-fileupload/src/.DS_Store | Bin 6148 -> 0 bytes .../src/com/fr/third/.DS_Store | Bin 6148 -> 0 bytes 7 files changed, 108 deletions(-) delete mode 100644 fine-commons-fileupload/.DS_Store delete mode 100755 fine-commons-fileupload/.classpath delete mode 100755 fine-commons-fileupload/.project delete mode 100755 fine-commons-fileupload/.settings/org.eclipse.jdt.core.prefs delete mode 100755 fine-commons-fileupload/.settings/org.eclipse.jdt.ui.prefs delete mode 100644 fine-commons-fileupload/src/.DS_Store delete mode 100644 fine-commons-fileupload/src/com/fr/third/.DS_Store diff --git a/fine-commons-fileupload/.DS_Store b/fine-commons-fileupload/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 - - - - - - - diff --git a/fine-commons-fileupload/.project b/fine-commons-fileupload/.project deleted file mode 100755 index 647b3367a..000000000 --- a/fine-commons-fileupload/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - commons-fileupload - The FileUpload component provides a simple yet flexible means of adding support for multipart file upload functionality to servlets and web applications. - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.maven.ide.eclipse.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.maven.ide.eclipse.maven2Nature - - diff --git a/fine-commons-fileupload/.settings/org.eclipse.jdt.core.prefs b/fine-commons-fileupload/.settings/org.eclipse.jdt.core.prefs deleted file mode 100755 index f16412c6a..000000000 --- a/fine-commons-fileupload/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,74 +0,0 @@ -#Mon Aug 07 02:40:54 CEST 2006 -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.1 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.3 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.doc.comment.support=enabled -org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning -org.eclipse.jdt.core.compiler.problem.assertIdentifier=ignore -org.eclipse.jdt.core.compiler.problem.autoboxing=warning -org.eclipse.jdt.core.compiler.problem.deprecation=warning -org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled -org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled -org.eclipse.jdt.core.compiler.problem.discouragedReference=warning -org.eclipse.jdt.core.compiler.problem.emptyStatement=warning -org.eclipse.jdt.core.compiler.problem.enumIdentifier=ignore -org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning -org.eclipse.jdt.core.compiler.problem.fieldHiding=warning -org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning -org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning -org.eclipse.jdt.core.compiler.problem.forbiddenReference=error -org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning -org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning -org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning -org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning -org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error -org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=disabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning -org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning -org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public -org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning -org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning -org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning -org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning -org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore -org.eclipse.jdt.core.compiler.problem.nullReference=warning -org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning -org.eclipse.jdt.core.compiler.problem.parameterAssignment=warning -org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error -org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning -org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled -org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning -org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore -org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning -org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning -org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning -org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning -org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning -org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning -org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled -org.eclipse.jdt.core.compiler.problem.unusedImport=warning -org.eclipse.jdt.core.compiler.problem.unusedLabel=warning -org.eclipse.jdt.core.compiler.problem.unusedLocal=warning -org.eclipse.jdt.core.compiler.problem.unusedParameter=warning -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled -org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning -org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.3 diff --git a/fine-commons-fileupload/.settings/org.eclipse.jdt.ui.prefs b/fine-commons-fileupload/.settings/org.eclipse.jdt.ui.prefs deleted file mode 100755 index c8d0df1d6..000000000 --- a/fine-commons-fileupload/.settings/org.eclipse.jdt.ui.prefs +++ /dev/null @@ -1,3 +0,0 @@ -#Sun Jun 18 21:36:57 CEST 2006 -eclipse.preferences.version=1 -internal.default.compliance=default diff --git a/fine-commons-fileupload/src/.DS_Store b/fine-commons-fileupload/src/.DS_Store deleted file mode 100644 index 992d869836a6e7daff929b0f46ce1eff98f21e2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJ5Iwu5S<|@BGIIz+$*G}Sw-dqxd4(aL4oBIq<6rPzB%pOb1+8*r~nn90#x9?D1e@Awz>@DN(HC@ z75GxXz7GX%SQFGSprxr0IZ2~AR;ggDln*;BZdYY@sf2laSjZ+ zXbvBmch;Ox)bEb-i!yIr@%D`jtO yUe0=Lf#1SE4Ygj5#al7ZTQN4)im!gu6}v{hCeDFQN8IT^{s@>ZG%D~L3VZ-s=qHH) diff --git a/fine-commons-fileupload/src/com/fr/third/.DS_Store b/fine-commons-fileupload/src/com/fr/third/.DS_Store deleted file mode 100644 index 7c8577a5fbea0551c707d4d59791d5665a08a0ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~J#GR)427Qq1yY(VDW}N+xWNd)3Ag|S5={gW5~=6tdH!j#NsU&~vt+-qXRY1+ zik&q8+dnTmU;?nAyW+#c%#8U8cRX Date: Tue, 5 Jun 2018 17:21:21 +0800 Subject: [PATCH 03/18] fix --- .../org/apache/commons/fileupload/disk/DiskFileItem.java | 6 +++--- .../commons/fileupload/servlet/FileCleanerCleanup.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItem.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItem.java index c5209e255..2bd3d93b9 100755 --- a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItem.java +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/disk/DiskFileItem.java @@ -29,9 +29,9 @@ import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.io.UnsupportedEncodingException; import java.util.Map; -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.FileCleaner; -import org.apache.commons.io.output.DeferredFileOutputStream; +import com.fr.third.org.apache.commons.io.IOUtils; +import com.fr.third.org.apache.commons.io.FileCleaner; +import com.fr.third.org.apache.commons.io.output.DeferredFileOutputStream; import com.fr.third.org.apache.commons.fileupload.FileItem; import com.fr.third.org.apache.commons.fileupload.FileUploadException; diff --git a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java index 331da41ce..c1270a181 100755 --- a/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java +++ b/fine-commons-fileupload/src/com/fr/third/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java @@ -19,7 +19,7 @@ package com.fr.third.org.apache.commons.fileupload.servlet; import javax.servlet.ServletContextListener; import javax.servlet.ServletContextEvent; -import org.apache.commons.io.FileCleaner; +import com.fr.third.org.apache.commons.io.FileCleaner; /** From 628bd6e6730f8c5759a3203e8ea55091e9d1acec Mon Sep 17 00:00:00 2001 From: hzzz Date: Tue, 5 Jun 2018 17:25:26 +0800 Subject: [PATCH 04/18] ant --- build.third_step1.gradle | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/build.third_step1.gradle b/build.third_step1.gradle index d09767981..4023171c1 100644 --- a/build.third_step1.gradle +++ b/build.third_step1.gradle @@ -17,7 +17,7 @@ def srcDir="." task unJar{ - ant{ + ant{ delete(dir:"build") } } @@ -30,8 +30,9 @@ sourceSets{ srcDirs=[ "${srcDir}/fine-poi/src", "${srcDir}/fine-quartz/src", - "${srcDir}/fine-commons-io/src" - ] + "${srcDir}/fine-commons-io/src", + "${srcDir}/fine-commons-fileupload/src" + ] } } @@ -40,7 +41,7 @@ sourceSets{ sourceSets.main.output.classesDir = file('build/classes/1') repositories{ mavenCentral() -} +} //获取什么分支名 FileTree files =fileTree(dir:'./',include:'build.*.gradle') @@ -75,6 +76,7 @@ task copyFiles(type:Copy,dependsOn:'compileJava'){ with dataContent.call("${srcDir}/fine-poi/src") with dataContent.call("${srcDir}/fine-quartz/src") with dataContent.call("${srcDir}/fine-commons-io/src") + with dataContent.call("${srcDir}/fine-commons-fileupload/src") into "${classesDir}" } } From a6c18a8b36da86e77997038733371f87a254eb25 Mon Sep 17 00:00:00 2001 From: richie Date: Tue, 5 Jun 2018 18:19:15 +0800 Subject: [PATCH 05/18] =?UTF-8?q?apache=20lang3=E5=92=8Ccollection4?= =?UTF-8?q?=E7=BC=96=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.third_step1.gradle | 4 +- .../commons/collections4/ArrayStack.java | 167 + .../org/apache/commons/collections4/Bag.java | 221 + .../apache/commons/collections4/BagUtils.java | 262 + .../apache/commons/collections4/BidiMap.java | 144 + .../collections4/BoundedCollection.java | 50 + .../commons/collections4/BoundedMap.java | 45 + .../apache/commons/collections4/Closure.java | 44 + .../commons/collections4/ClosureUtils.java | 376 + .../commons/collections4/CollectionUtils.java | 1892 ++++ .../commons/collections4/ComparatorUtils.java | 239 + .../collections4/EnumerationUtils.java | 97 + .../apache/commons/collections4/Equator.java | 43 + .../apache/commons/collections4/Factory.java | 44 + .../commons/collections4/FactoryUtils.java | 150 + .../commons/collections4/FluentIterable.java | 505 ++ .../collections4/FunctorException.java | 72 + .../org/apache/commons/collections4/Get.java | 77 + .../commons/collections4/IterableGet.java | 47 + .../commons/collections4/IterableMap.java | 43 + .../collections4/IterableSortedMap.java | 31 + .../commons/collections4/IterableUtils.java | 1086 +++ .../commons/collections4/IteratorUtils.java | 1526 ++++ .../apache/commons/collections4/KeyValue.java | 47 + .../commons/collections4/ListUtils.java | 699 ++ .../commons/collections4/ListValuedMap.java | 67 + .../commons/collections4/MapIterator.java | 109 + .../apache/commons/collections4/MapUtils.java | 1797 ++++ .../apache/commons/collections4/MultiMap.java | 158 + .../commons/collections4/MultiMapUtils.java | 254 + .../apache/commons/collections4/MultiSet.java | 272 + .../commons/collections4/MultiSetUtils.java | 119 + .../commons/collections4/MultiValuedMap.java | 320 + .../commons/collections4/OrderedBidiMap.java | 51 + .../commons/collections4/OrderedIterator.java | 47 + .../commons/collections4/OrderedMap.java | 73 + .../collections4/OrderedMapIterator.java | 46 + .../commons/collections4/Predicate.java | 49 + .../commons/collections4/PredicateUtils.java | 540 ++ .../org/apache/commons/collections4/Put.java | 55 + .../commons/collections4/QueueUtils.java | 108 + .../collections4/ResettableIterator.java | 38 + .../collections4/ResettableListIterator.java | 32 + .../apache/commons/collections4/SetUtils.java | 645 ++ .../commons/collections4/SetValuedMap.java | 65 + .../commons/collections4/SortedBag.java | 53 + .../commons/collections4/SortedBidiMap.java | 58 + .../commons/collections4/SplitMapUtils.java | 247 + .../commons/collections4/Transformer.java | 51 + .../collections4/TransformerUtils.java | 484 + .../org/apache/commons/collections4/Trie.java | 47 + .../commons/collections4/TrieUtils.java | 49 + .../commons/collections4/Unmodifiable.java | 38 + .../bag/AbstractBagDecorator.java | 98 + .../collections4/bag/AbstractMapBag.java | 624 ++ .../bag/AbstractSortedBagDecorator.java | 82 + .../collections4/bag/CollectionBag.java | 243 + .../collections4/bag/CollectionSortedBag.java | 168 + .../commons/collections4/bag/HashBag.java | 78 + .../collections4/bag/PredicatedBag.java | 125 + .../collections4/bag/PredicatedSortedBag.java | 108 + .../collections4/bag/SynchronizedBag.java | 151 + .../bag/SynchronizedSortedBag.java | 108 + .../collections4/bag/TransformedBag.java | 148 + .../bag/TransformedSortedBag.java | 131 + .../commons/collections4/bag/TreeBag.java | 140 + .../collections4/bag/UnmodifiableBag.java | 158 + .../bag/UnmodifiableSortedBag.java | 155 + .../collections4/bag/package-info.java | 39 + .../bidimap/AbstractBidiMapDecorator.java | 89 + .../bidimap/AbstractDualBidiMap.java | 805 ++ .../AbstractOrderedBidiMapDecorator.java | 92 + .../AbstractSortedBidiMapDecorator.java | 93 + .../collections4/bidimap/DualHashBidiMap.java | 107 + .../bidimap/DualLinkedHashBidiMap.java | 101 + .../collections4/bidimap/DualTreeBidiMap.java | 416 + .../collections4/bidimap/TreeBidiMap.java | 2228 +++++ .../bidimap/UnmodifiableBidiMap.java | 136 + .../bidimap/UnmodifiableOrderedBidiMap.java | 147 + .../bidimap/UnmodifiableSortedBidiMap.java | 158 + .../collections4/bidimap/package-info.java | 41 + .../AbstractCollectionDecorator.java | 178 + .../collection/CompositeCollection.java | 481 + .../collection/IndexedCollection.java | 260 + .../collection/PredicatedCollection.java | 444 + .../collection/SynchronizedCollection.java | 232 + .../collection/TransformedCollection.java | 155 + .../UnmodifiableBoundedCollection.java | 166 + .../collection/UnmodifiableCollection.java | 111 + .../collections4/collection/package-info.java | 36 + .../comparators/BooleanComparator.java | 192 + .../comparators/ComparableComparator.java | 130 + .../comparators/ComparatorChain.java | 349 + .../comparators/FixedOrderComparator.java | 299 + .../comparators/NullComparator.java | 181 + .../comparators/ReverseComparator.java | 124 + .../comparators/TransformingComparator.java | 131 + .../comparators/package-info.java | 28 + .../functors/AbstractQuantifierPredicate.java | 56 + .../collections4/functors/AllPredicate.java | 115 + .../collections4/functors/AndPredicate.java | 90 + .../collections4/functors/AnyPredicate.java | 112 + .../functors/CatchAndRethrowClosure.java | 76 + .../collections4/functors/ChainedClosure.java | 126 + .../functors/ChainedTransformer.java | 128 + .../functors/CloneTransformer.java | 73 + .../functors/ClosureTransformer.java | 86 + .../functors/ComparatorPredicate.java | 191 + .../functors/ConstantFactory.java | 90 + .../functors/ConstantTransformer.java | 129 + .../collections4/functors/DefaultEquator.java | 83 + .../collections4/functors/EqualPredicate.java | 122 + .../functors/ExceptionClosure.java | 72 + .../functors/ExceptionFactory.java | 72 + .../functors/ExceptionPredicate.java | 73 + .../functors/ExceptionTransformer.java | 74 + .../functors/FactoryTransformer.java | 86 + .../collections4/functors/FalsePredicate.java | 71 + .../collections4/functors/ForClosure.java | 106 + .../collections4/functors/FunctorUtils.java | 207 + .../functors/IdentityPredicate.java | 84 + .../collections4/functors/IfClosure.java | 156 + .../collections4/functors/IfTransformer.java | 154 + .../functors/InstanceofPredicate.java | 83 + .../functors/InstantiateFactory.java | 140 + .../functors/InstantiateTransformer.java | 131 + .../functors/InvokerTransformer.java | 147 + .../collections4/functors/MapTransformer.java | 87 + .../collections4/functors/NOPClosure.java | 70 + .../collections4/functors/NOPTransformer.java | 71 + .../collections4/functors/NonePredicate.java | 102 + .../functors/NotNullPredicate.java | 71 + .../collections4/functors/NotPredicate.java | 84 + .../functors/NullIsExceptionPredicate.java | 90 + .../functors/NullIsFalsePredicate.java | 88 + .../functors/NullIsTruePredicate.java | 88 + .../collections4/functors/NullPredicate.java | 71 + .../collections4/functors/OnePredicate.java | 107 + .../collections4/functors/OrPredicate.java | 90 + .../functors/PredicateDecorator.java | 41 + .../functors/PredicateTransformer.java | 85 + .../functors/PrototypeFactory.java | 207 + .../functors/StringValueTransformer.java | 71 + .../collections4/functors/SwitchClosure.java | 186 + .../functors/SwitchTransformer.java | 199 + .../functors/TransformedPredicate.java | 107 + .../functors/TransformerClosure.java | 85 + .../functors/TransformerPredicate.java | 91 + .../collections4/functors/TruePredicate.java | 71 + .../functors/UniquePredicate.java | 70 + .../collections4/functors/WhileClosure.java | 124 + .../collections4/functors/package-info.java | 43 + .../iterators/AbstractEmptyIterator.java | 76 + .../iterators/AbstractEmptyMapIterator.java | 46 + .../iterators/AbstractIteratorDecorator.java | 47 + .../AbstractListIteratorDecorator.java | 105 + .../AbstractMapIteratorDecorator.java | 90 + .../AbstractOrderedMapIteratorDecorator.java | 100 + .../AbstractUntypedIteratorDecorator.java | 66 + .../collections4/iterators/ArrayIterator.java | 200 + .../iterators/ArrayListIterator.java | 204 + .../iterators/BoundedIterator.java | 134 + .../iterators/CollatingIterator.java | 397 + .../collections4/iterators/EmptyIterator.java | 76 + .../iterators/EmptyListIterator.java | 77 + .../iterators/EmptyMapIterator.java | 56 + .../iterators/EmptyOrderedIterator.java | 55 + .../iterators/EmptyOrderedMapIterator.java | 56 + .../iterators/EntrySetMapIterator.java | 169 + .../iterators/EnumerationIterator.java | 138 + .../iterators/FilterIterator.java | 185 + .../iterators/FilterListIterator.java | 274 + .../collections4/iterators/IteratorChain.java | 278 + .../iterators/IteratorEnumeration.java | 95 + .../iterators/IteratorIterable.java | 129 + .../iterators/LazyIteratorChain.java | 150 + .../iterators/ListIteratorWrapper.java | 262 + .../iterators/LoopingIterator.java | 125 + .../iterators/LoopingListIterator.java | 254 + .../iterators/NodeListIterator.java | 88 + .../iterators/ObjectArrayIterator.java | 174 + .../iterators/ObjectArrayListIterator.java | 194 + .../iterators/ObjectGraphIterator.java | 252 + .../iterators/PeekingIterator.java | 159 + .../iterators/PermutationIterator.java | 156 + .../iterators/PushbackIterator.java | 107 + .../iterators/ReverseListIterator.java | 177 + .../iterators/SingletonIterator.java | 123 + .../iterators/SingletonListIterator.java | 172 + .../iterators/SkippingIterator.java | 96 + .../iterators/TransformIterator.java | 144 + .../iterators/UniqueFilterIterator.java | 44 + .../iterators/UnmodifiableIterator.java | 83 + .../iterators/UnmodifiableListIterator.java | 105 + .../iterators/UnmodifiableMapIterator.java | 94 + .../UnmodifiableOrderedMapIterator.java | 104 + .../iterators/ZippingIterator.java | 158 + .../collections4/iterators/package-info.java | 28 + .../keyvalue/AbstractKeyValue.java | 91 + .../keyvalue/AbstractMapEntry.java | 91 + .../keyvalue/AbstractMapEntryDecorator.java | 89 + .../keyvalue/DefaultKeyValue.java | 154 + .../keyvalue/DefaultMapEntry.java | 62 + .../collections4/keyvalue/MultiKey.java | 281 + .../collections4/keyvalue/TiedMapEntry.java | 137 + .../keyvalue/UnmodifiableMapEntry.java | 75 + .../collections4/keyvalue/package-info.java | 30 + .../collections4/list/AbstractLinkedList.java | 1067 +++ .../list/AbstractListDecorator.java | 120 + .../AbstractSerializableListDecorator.java | 72 + .../list/CursorableLinkedList.java | 620 ++ .../collections4/list/FixedSizeList.java | 182 + .../commons/collections4/list/GrowthList.java | 189 + .../commons/collections4/list/LazyList.java | 141 + .../list/NodeCachingLinkedList.java | 244 + .../collections4/list/PredicatedList.java | 184 + .../collections4/list/SetUniqueList.java | 424 + .../collections4/list/TransformedList.java | 202 + .../commons/collections4/list/TreeList.java | 1120 +++ .../collections4/list/UnmodifiableList.java | 148 + .../collections4/list/package-info.java | 40 + .../collections4/map/AbstractHashedMap.java | 1389 +++ .../map/AbstractInputCheckedMapDecorator.java | 206 + .../collections4/map/AbstractIterableMap.java | 36 + .../collections4/map/AbstractLinkedMap.java | 605 ++ .../map/AbstractMapDecorator.java | 143 + .../map/AbstractOrderedMapDecorator.java | 90 + .../map/AbstractReferenceMap.java | 1058 +++ .../map/AbstractSortedMapDecorator.java | 163 + .../collections4/map/CaseInsensitiveMap.java | 161 + .../collections4/map/CompositeMap.java | 548 ++ .../collections4/map/DefaultedMap.java | 210 + .../map/EntrySetToMapIteratorAdapter.java | 113 + .../collections4/map/FixedSizeMap.java | 170 + .../collections4/map/FixedSizeSortedMap.java | 182 + .../commons/collections4/map/Flat3Map.java | 1249 +++ .../commons/collections4/map/HashedMap.java | 115 + .../commons/collections4/map/LRUMap.java | 522 ++ .../commons/collections4/map/LazyMap.java | 174 + .../collections4/map/LazySortedMap.java | 157 + .../commons/collections4/map/LinkedMap.java | 303 + .../collections4/map/ListOrderedMap.java | 786 ++ .../commons/collections4/map/MultiKeyMap.java | 908 ++ .../collections4/map/MultiValueMap.java | 577 ++ .../collections4/map/PassiveExpiringMap.java | 538 ++ .../collections4/map/PredicatedMap.java | 192 + .../collections4/map/PredicatedSortedMap.java | 128 + .../map/ReferenceIdentityMap.java | 240 + .../collections4/map/ReferenceMap.java | 184 + .../collections4/map/SingletonMap.java | 576 ++ .../collections4/map/StaticBucketMap.java | 718 ++ .../collections4/map/TransformedMap.java | 246 + .../map/TransformedSortedMap.java | 159 + .../map/UnmodifiableEntrySet.java | 183 + .../collections4/map/UnmodifiableMap.java | 159 + .../map/UnmodifiableOrderedMap.java | 153 + .../map/UnmodifiableSortedMap.java | 175 + .../collections4/map/package-info.java | 55 + .../multimap/AbstractListValuedMap.java | 289 + .../multimap/AbstractMultiValuedMap.java | 944 ++ .../AbstractMultiValuedMapDecorator.java | 188 + .../multimap/AbstractSetValuedMap.java | 135 + .../multimap/ArrayListValuedHashMap.java | 143 + .../multimap/HashSetValuedHashMap.java | 131 + .../multimap/TransformedMultiValuedMap.java | 191 + .../multimap/UnmodifiableMultiValuedMap.java | 152 + .../collections4/multimap/package-info.java | 35 + .../multiset/AbstractMapMultiSet.java | 564 ++ .../multiset/AbstractMultiSet.java | 509 ++ .../multiset/AbstractMultiSetDecorator.java | 107 + .../collections4/multiset/HashMultiSet.java | 77 + .../multiset/PredicatedMultiSet.java | 137 + .../multiset/SynchronizedMultiSet.java | 164 + .../multiset/UnmodifiableMultiSet.java | 166 + .../collections4/multiset/package-info.java | 38 + .../apache/commons/collections4/overview.html | 113 + .../commons/collections4/package-info.java | 27 + .../queue/AbstractQueueDecorator.java | 93 + .../collections4/queue/CircularFifoQueue.java | 420 + .../collections4/queue/PredicatedQueue.java | 118 + .../collections4/queue/TransformedQueue.java | 134 + .../collections4/queue/UnmodifiableQueue.java | 153 + .../collections4/queue/package-info.java | 34 + .../collections4/sequence/CommandVisitor.java | 143 + .../collections4/sequence/DeleteCommand.java | 55 + .../collections4/sequence/EditCommand.java | 82 + .../collections4/sequence/EditScript.java | 133 + .../collections4/sequence/InsertCommand.java | 57 + .../collections4/sequence/KeepCommand.java | 57 + .../sequence/ReplacementsFinder.java | 109 + .../sequence/ReplacementsHandler.java | 52 + .../sequence/SequencesComparator.java | 348 + .../collections4/sequence/package-info.java | 77 + .../set/AbstractNavigableSetDecorator.java | 122 + .../set/AbstractSerializableSetDecorator.java | 72 + .../set/AbstractSetDecorator.java | 76 + .../set/AbstractSortedSetDecorator.java | 92 + .../collections4/set/CompositeSet.java | 498 ++ .../collections4/set/ListOrderedSet.java | 409 + .../collections4/set/MapBackedSet.java | 166 + .../set/PredicatedNavigableSet.java | 152 + .../collections4/set/PredicatedSet.java | 99 + .../collections4/set/PredicatedSortedSet.java | 122 + .../set/TransformedNavigableSet.java | 173 + .../collections4/set/TransformedSet.java | 114 + .../set/TransformedSortedSet.java | 144 + .../set/UnmodifiableNavigableSet.java | 182 + .../collections4/set/UnmodifiableSet.java | 109 + .../set/UnmodifiableSortedSet.java | 153 + .../collections4/set/package-info.java | 41 + .../AbstractIterableGetMapDecorator.java | 129 + .../splitmap/TransformedSplitMap.java | 213 + .../collections4/splitmap/package-info.java | 39 + .../trie/AbstractBitwiseTrie.java | 213 + .../trie/AbstractPatriciaTrie.java | 2406 +++++ .../collections4/trie/KeyAnalyzer.java | 148 + .../collections4/trie/PatriciaTrie.java | 69 + .../collections4/trie/UnmodifiableTrie.java | 187 + .../trie/analyzer/StringKeyAnalyzer.java | 136 + .../trie/analyzer/package-info.java | 22 + .../collections4/trie/package-info.java | 37 + .../apache/commons/lang3/AnnotationUtils.java | 371 + .../org/apache/commons/lang3/ArrayUtils.java | 6343 +++++++++++++ .../org/apache/commons/lang3/BitField.java | 332 + .../apache/commons/lang3/BooleanUtils.java | 1109 +++ .../apache/commons/lang3/CharEncoding.java | 105 + .../org/apache/commons/lang3/CharRange.java | 360 + .../commons/lang3/CharSequenceUtils.java | 216 + .../org/apache/commons/lang3/CharSet.java | 278 + .../apache/commons/lang3/CharSetUtils.java | 251 + .../org/apache/commons/lang3/CharUtils.java | 553 ++ .../apache/commons/lang3/ClassPathUtils.java | 139 + .../org/apache/commons/lang3/ClassUtils.java | 1320 +++ .../org/apache/commons/lang3/Conversion.java | 1594 ++++ .../org/apache/commons/lang3/EnumUtils.java | 311 + .../org/apache/commons/lang3/JavaVersion.java | 220 + .../org/apache/commons/lang3/LocaleUtils.java | 328 + .../lang3/NotImplementedException.java | 128 + .../org/apache/commons/lang3/ObjectUtils.java | 951 ++ .../commons/lang3/RandomStringUtils.java | 335 + .../org/apache/commons/lang3/RandomUtils.java | 174 + .../third/org/apache/commons/lang3/Range.java | 496 ++ .../commons/lang3/SerializationException.java | 78 + .../commons/lang3/SerializationUtils.java | 338 + .../commons/lang3/StringEscapeUtils.java | 805 ++ .../org/apache/commons/lang3/StringUtils.java | 7879 +++++++++++++++++ .../org/apache/commons/lang3/SystemUtils.java | 1663 ++++ .../org/apache/commons/lang3/Validate.java | 1256 +++ .../apache/commons/lang3/builder/Builder.java | 89 + .../lang3/builder/CompareToBuilder.java | 1029 +++ .../apache/commons/lang3/builder/Diff.java | 118 + .../commons/lang3/builder/DiffBuilder.java | 958 ++ .../commons/lang3/builder/DiffResult.java | 208 + .../commons/lang3/builder/Diffable.java | 54 + .../commons/lang3/builder/EqualsBuilder.java | 954 ++ .../lang3/builder/HashCodeBuilder.java | 971 ++ .../apache/commons/lang3/builder/IDKey.java | 74 + .../MultilineRecursiveToStringStyle.java | 220 + .../lang3/builder/RecursiveToStringStyle.java | 100 + .../builder/ReflectionToStringBuilder.java | 696 ++ .../lang3/builder/StandardToStringStyle.java | 560 ++ .../lang3/builder/ToStringBuilder.java | 1080 +++ .../commons/lang3/builder/ToStringStyle.java | 2614 ++++++ .../commons/lang3/builder/package-info.java | 34 + .../lang3/concurrent/AtomicInitializer.java | 107 + .../concurrent/AtomicSafeInitializer.java | 97 + .../concurrent/BackgroundInitializer.java | 335 + .../lang3/concurrent/BasicThreadFactory.java | 383 + .../CallableBackgroundInitializer.java | 126 + .../lang3/concurrent/ConcurrentException.java | 70 + .../concurrent/ConcurrentInitializer.java | 54 + .../ConcurrentRuntimeException.java | 72 + .../lang3/concurrent/ConcurrentUtils.java | 393 + .../lang3/concurrent/ConstantInitializer.java | 130 + .../lang3/concurrent/LazyInitializer.java | 122 + .../MultiBackgroundInitializer.java | 352 + .../lang3/concurrent/TimedSemaphore.java | 424 + .../lang3/concurrent/package-info.java | 440 + .../lang3/event/EventListenerSupport.java | 322 + .../commons/lang3/event/EventUtils.java | 130 + .../commons/lang3/event/package-info.java | 23 + .../lang3/exception/CloneFailedException.java | 62 + .../lang3/exception/ContextedException.java | 256 + .../exception/ContextedRuntimeException.java | 257 + .../exception/DefaultExceptionContext.java | 165 + .../lang3/exception/ExceptionContext.java | 103 + .../lang3/exception/ExceptionUtils.java | 697 ++ .../commons/lang3/exception/package-info.java | 27 + .../apache/commons/lang3/math/Fraction.java | 942 ++ .../commons/lang3/math/IEEE754rUtils.java | 268 + .../commons/lang3/math/NumberUtils.java | 1602 ++++ .../commons/lang3/math/package-info.java | 34 + .../apache/commons/lang3/mutable/Mutable.java | 54 + .../commons/lang3/mutable/MutableBoolean.java | 215 + .../commons/lang3/mutable/MutableByte.java | 287 + .../commons/lang3/mutable/MutableDouble.java | 314 + .../commons/lang3/mutable/MutableFloat.java | 315 + .../commons/lang3/mutable/MutableInt.java | 277 + .../commons/lang3/mutable/MutableLong.java | 277 + .../commons/lang3/mutable/MutableObject.java | 128 + .../commons/lang3/mutable/MutableShort.java | 287 + .../commons/lang3/mutable/package-info.java | 25 + .../apache/commons/lang3/package-info.java | 125 + .../lang3/reflect/ConstructorUtils.java | 300 + .../commons/lang3/reflect/FieldUtils.java | 840 ++ .../lang3/reflect/InheritanceUtils.java | 67 + .../commons/lang3/reflect/MemberUtils.java | 186 + .../commons/lang3/reflect/MethodUtils.java | 639 ++ .../commons/lang3/reflect/TypeLiteral.java | 124 + .../commons/lang3/reflect/TypeUtils.java | 1848 ++++ .../apache/commons/lang3/reflect/Typed.java | 35 + .../commons/lang3/reflect/package-info.java | 24 + .../commons/lang3/text/CompositeFormat.java | 116 + .../lang3/text/ExtendedMessageFormat.java | 529 ++ .../commons/lang3/text/FormatFactory.java | 42 + .../commons/lang3/text/FormattableUtils.java | 151 + .../apache/commons/lang3/text/StrBuilder.java | 3127 +++++++ .../apache/commons/lang3/text/StrLookup.java | 192 + .../apache/commons/lang3/text/StrMatcher.java | 436 + .../commons/lang3/text/StrSubstitutor.java | 1196 +++ .../commons/lang3/text/StrTokenizer.java | 1114 +++ .../apache/commons/lang3/text/WordUtils.java | 585 ++ .../commons/lang3/text/package-info.java | 28 + .../text/translate/AggregateTranslator.java | 60 + .../translate/CharSequenceTranslator.java | 136 + .../text/translate/CodePointTranslator.java | 52 + .../lang3/text/translate/EntityArrays.java | 425 + .../text/translate/JavaUnicodeEscaper.java | 114 + .../text/translate/LookupTranslator.java | 92 + .../text/translate/NumericEntityEscaper.java | 119 + .../translate/NumericEntityUnescaper.java | 139 + .../lang3/text/translate/OctalUnescaper.java | 80 + .../lang3/text/translate/UnicodeEscaper.java | 139 + .../text/translate/UnicodeUnescaper.java | 65 + .../UnicodeUnpairedSurrogateRemover.java | 42 + .../lang3/text/translate/package-info.java | 25 + .../commons/lang3/time/DateFormatUtils.java | 323 + .../apache/commons/lang3/time/DateParser.java | 105 + .../commons/lang3/time/DatePrinter.java | 125 + .../apache/commons/lang3/time/DateUtils.java | 1855 ++++ .../lang3/time/DurationFormatUtils.java | 692 ++ .../commons/lang3/time/FastDateFormat.java | 615 ++ .../commons/lang3/time/FastDateParser.java | 931 ++ .../commons/lang3/time/FastDatePrinter.java | 1380 +++ .../commons/lang3/time/FormatCache.java | 266 + .../apache/commons/lang3/time/StopWatch.java | 478 + .../commons/lang3/time/package-info.java | 29 + .../commons/lang3/tuple/ImmutablePair.java | 104 + .../commons/lang3/tuple/ImmutableTriple.java | 105 + .../commons/lang3/tuple/MutablePair.java | 124 + .../commons/lang3/tuple/MutableTriple.java | 134 + .../org/apache/commons/lang3/tuple/Pair.java | 180 + .../apache/commons/lang3/tuple/Triple.java | 162 + .../commons/lang3/tuple/package-info.java | 23 + 454 files changed, 136601 insertions(+), 1 deletion(-) create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/ArrayStack.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/Bag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/BagUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/BidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/BoundedCollection.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/BoundedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/Closure.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/ClosureUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/CollectionUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/ComparatorUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/EnumerationUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/Equator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/Factory.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/FactoryUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/FluentIterable.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/FunctorException.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/Get.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/IterableGet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/IterableMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/IterableSortedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/IterableUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/IteratorUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/KeyValue.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/ListUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/ListValuedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/MapIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/MapUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/MultiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/MultiMapUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/MultiSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/MultiSetUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/MultiValuedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/OrderedBidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/OrderedIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/OrderedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/OrderedMapIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/Predicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/PredicateUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/Put.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/QueueUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/ResettableIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/ResettableListIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/SetUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/SetValuedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/SortedBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/SortedBidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/SplitMapUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/Transformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/TransformerUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/Trie.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/TrieUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/Unmodifiable.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractBagDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractMapBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractSortedBagDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/CollectionBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/CollectionSortedBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/HashBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/PredicatedBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/PredicatedSortedBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/SynchronizedBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/SynchronizedSortedBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/TransformedBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/TransformedSortedBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/TreeBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/UnmodifiableBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/UnmodifiableSortedBag.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bag/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractBidiMapDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractDualBidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractOrderedBidiMapDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractSortedBidiMapDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualHashBidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualLinkedHashBidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualTreeBidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/TreeBidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableBidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableOrderedBidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableSortedBidiMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/bidimap/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/collection/AbstractCollectionDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/collection/CompositeCollection.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/collection/IndexedCollection.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/collection/PredicatedCollection.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/collection/SynchronizedCollection.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/collection/TransformedCollection.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/collection/UnmodifiableBoundedCollection.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/collection/UnmodifiableCollection.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/collection/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/comparators/BooleanComparator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/comparators/ComparableComparator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/comparators/ComparatorChain.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/comparators/FixedOrderComparator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/comparators/NullComparator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/comparators/ReverseComparator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/comparators/TransformingComparator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/comparators/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/AbstractQuantifierPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/AllPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/AndPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/AnyPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/CatchAndRethrowClosure.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ChainedClosure.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ChainedTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/CloneTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ClosureTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ComparatorPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ConstantFactory.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ConstantTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/DefaultEquator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/EqualPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionClosure.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionFactory.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/FactoryTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/FalsePredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/ForClosure.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/FunctorUtils.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/IdentityPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/IfClosure.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/IfTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/InstanceofPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/InstantiateFactory.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/InstantiateTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/InvokerTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/MapTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/NOPClosure.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/NOPTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/NonePredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/NotNullPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/NotPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsExceptionPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsFalsePredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsTruePredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/NullPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/OnePredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/OrPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/PredicateDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/PredicateTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/PrototypeFactory.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/StringValueTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/SwitchClosure.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/SwitchTransformer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformedPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformerClosure.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformerPredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/TruePredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/UniquePredicate.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/WhileClosure.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/functors/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractEmptyIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractEmptyMapIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractIteratorDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractListIteratorDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractMapIteratorDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractOrderedMapIteratorDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractUntypedIteratorDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/ArrayIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/ArrayListIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/BoundedIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/CollatingIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyListIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyMapIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyOrderedIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyOrderedMapIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/EntrySetMapIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/EnumerationIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/FilterIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/FilterListIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorChain.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorEnumeration.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorIterable.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/LazyIteratorChain.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/ListIteratorWrapper.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/LoopingIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/LoopingListIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/NodeListIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectArrayIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectArrayListIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectGraphIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/PeekingIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/PermutationIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/PushbackIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/ReverseListIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/SingletonIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/SingletonListIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/SkippingIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/TransformIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/UniqueFilterIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableListIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableMapIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableOrderedMapIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/ZippingIterator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/iterators/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractKeyValue.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractMapEntry.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractMapEntryDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/DefaultKeyValue.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/DefaultMapEntry.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/MultiKey.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/TiedMapEntry.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/UnmodifiableMapEntry.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractLinkedList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractListDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractSerializableListDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/CursorableLinkedList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/FixedSizeList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/GrowthList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/LazyList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/NodeCachingLinkedList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/PredicatedList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/SetUniqueList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/TransformedList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/TreeList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/UnmodifiableList.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/list/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractHashedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractInputCheckedMapDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractIterableMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractLinkedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractMapDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractOrderedMapDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractReferenceMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractSortedMapDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/CaseInsensitiveMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/CompositeMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/DefaultedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/EntrySetToMapIteratorAdapter.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/FixedSizeMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/FixedSizeSortedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/Flat3Map.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/HashedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/LRUMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/LazyMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/LazySortedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/LinkedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/ListOrderedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/MultiKeyMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/MultiValueMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/PassiveExpiringMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/PredicatedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/PredicatedSortedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/ReferenceIdentityMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/ReferenceMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/SingletonMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/StaticBucketMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/TransformedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/TransformedSortedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableEntrySet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableOrderedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableSortedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/map/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractListValuedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractSetValuedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multimap/ArrayListValuedHashMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multimap/HashSetValuedHashMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multimap/TransformedMultiValuedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multimap/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMapMultiSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMultiSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMultiSetDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multiset/HashMultiSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multiset/PredicatedMultiSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multiset/SynchronizedMultiSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multiset/UnmodifiableMultiSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/multiset/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/overview.html create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/queue/AbstractQueueDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/queue/CircularFifoQueue.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/queue/PredicatedQueue.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/queue/TransformedQueue.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/queue/UnmodifiableQueue.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/queue/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/sequence/CommandVisitor.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/sequence/DeleteCommand.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/sequence/EditCommand.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/sequence/EditScript.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/sequence/InsertCommand.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/sequence/KeepCommand.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/sequence/ReplacementsFinder.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/sequence/ReplacementsHandler.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/sequence/SequencesComparator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/sequence/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractNavigableSetDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSerializableSetDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSetDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSortedSetDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/CompositeSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/ListOrderedSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/MapBackedSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedNavigableSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedSortedSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedNavigableSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedSortedSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableNavigableSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableSortedSet.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/set/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/splitmap/AbstractIterableGetMapDecorator.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/splitmap/TransformedSplitMap.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/splitmap/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/trie/AbstractBitwiseTrie.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/trie/AbstractPatriciaTrie.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/trie/KeyAnalyzer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/trie/PatriciaTrie.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/trie/UnmodifiableTrie.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/trie/analyzer/StringKeyAnalyzer.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/trie/analyzer/package-info.java create mode 100644 fine-commons-collections4/src/org/apache/commons/collections4/trie/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/AnnotationUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ArrayUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/BitField.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/BooleanUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharEncoding.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharRange.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSequenceUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSet.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSetUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ClassPathUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ClassUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Conversion.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/EnumUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/JavaVersion.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/LocaleUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/NotImplementedException.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ObjectUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/RandomStringUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/RandomUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Range.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SerializationException.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SerializationUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/StringEscapeUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/StringUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SystemUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Validate.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Builder.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/CompareToBuilder.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Diff.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/DiffBuilder.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/DiffResult.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Diffable.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/EqualsBuilder.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/HashCodeBuilder.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/IDKey.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/RecursiveToStringStyle.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/StandardToStringStyle.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ToStringBuilder.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ToStringStyle.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/AtomicInitializer.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/BackgroundInitializer.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/BasicThreadFactory.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentException.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentInitializer.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConstantInitializer.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/LazyInitializer.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/TimedSemaphore.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/EventListenerSupport.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/EventUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/CloneFailedException.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ContextedException.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ContextedRuntimeException.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/DefaultExceptionContext.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ExceptionContext.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ExceptionUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/Fraction.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/IEEE754rUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/NumberUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/Mutable.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableBoolean.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableByte.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableDouble.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableFloat.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableInt.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableLong.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableObject.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableShort.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/ConstructorUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/FieldUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/InheritanceUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/MemberUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/MethodUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/TypeLiteral.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/TypeUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/Typed.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/CompositeFormat.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/ExtendedMessageFormat.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/FormatFactory.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/FormattableUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrBuilder.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrLookup.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrMatcher.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrSubstitutor.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrTokenizer.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/WordUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/AggregateTranslator.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/CodePointTranslator.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/EntityArrays.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/JavaUnicodeEscaper.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/LookupTranslator.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/OctalUnescaper.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/UnicodeEscaper.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/UnicodeUnescaper.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/UnicodeUnpairedSurrogateRemover.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/DateFormatUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/DateParser.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/DatePrinter.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/DateUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/DurationFormatUtils.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/FastDateFormat.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/FastDateParser.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/FastDatePrinter.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/FormatCache.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/StopWatch.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/time/package-info.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/tuple/ImmutablePair.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/tuple/ImmutableTriple.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/tuple/MutablePair.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/tuple/MutableTriple.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/tuple/Pair.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/tuple/Triple.java create mode 100644 fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/tuple/package-info.java diff --git a/build.third_step1.gradle b/build.third_step1.gradle index d09767981..71d9d72ce 100644 --- a/build.third_step1.gradle +++ b/build.third_step1.gradle @@ -30,7 +30,9 @@ sourceSets{ srcDirs=[ "${srcDir}/fine-poi/src", "${srcDir}/fine-quartz/src", - "${srcDir}/fine-commons-io/src" + "${srcDir}/fine-commons-io/src", + "${srcDir}/fine-commons-lang3/src", + "${srcDir}/fine-commons-collections4/src" ] } } diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/ArrayStack.java b/fine-commons-collections4/src/org/apache/commons/collections4/ArrayStack.java new file mode 100644 index 000000000..2b3b36eed --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/ArrayStack.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.ArrayList; +import java.util.EmptyStackException; + +/** + * An implementation of the {@link java.util.Stack} API that is based on an + * ArrayList instead of a Vector, so it is not + * synchronized to protect against multi-threaded access. The implementation + * is therefore operates faster in environments where you do not need to + * worry about multiple thread contention. + *

+ * The removal order of an ArrayStack is based on insertion + * order: The most recently added element is removed first. The iteration + * order is not the same as the removal order. The iterator returns + * elements from the bottom up. + *

+ * Unlike Stack, ArrayStack accepts null entries. + *

+ * Note: From version 4.0 onwards, this class does not implement the + * removed {@code Buffer} interface anymore. + * + * @see java.util.Stack + * @since 1.0 + * @version $Id: ArrayStack.java 1477779 2013-04-30 18:55:24Z tn $ + * @deprecated use {@link java.util.ArrayDeque} instead (available from Java 1.6) + */ +@Deprecated +public class ArrayStack extends ArrayList { + + /** Ensure serialization compatibility */ + private static final long serialVersionUID = 2130079159931574599L; + + /** + * Constructs a new empty ArrayStack. The initial size + * is controlled by ArrayList and is currently 10. + */ + public ArrayStack() { + super(); + } + + /** + * Constructs a new empty ArrayStack with an initial size. + * + * @param initialSize the initial size to use + * @throws IllegalArgumentException if the specified initial size + * is negative + */ + public ArrayStack(final int initialSize) { + super(initialSize); + } + + /** + * Return true if this stack is currently empty. + *

+ * This method exists for compatibility with java.util.Stack. + * New users of this class should use isEmpty instead. + * + * @return true if the stack is currently empty + */ + public boolean empty() { + return isEmpty(); + } + + /** + * Returns the top item off of this stack without removing it. + * + * @return the top item on the stack + * @throws EmptyStackException if the stack is empty + */ + public E peek() throws EmptyStackException { + final int n = size(); + if (n <= 0) { + throw new EmptyStackException(); + } else { + return get(n - 1); + } + } + + /** + * Returns the n'th item down (zero-relative) from the top of this + * stack without removing it. + * + * @param n the number of items down to go + * @return the n'th item on the stack, zero relative + * @throws EmptyStackException if there are not enough items on the + * stack to satisfy this request + */ + public E peek(final int n) throws EmptyStackException { + final int m = (size() - n) - 1; + if (m < 0) { + throw new EmptyStackException(); + } else { + return get(m); + } + } + + /** + * Pops the top item off of this stack and return it. + * + * @return the top item on the stack + * @throws EmptyStackException if the stack is empty + */ + public E pop() throws EmptyStackException { + final int n = size(); + if (n <= 0) { + throw new EmptyStackException(); + } else { + return remove(n - 1); + } + } + + /** + * Pushes a new item onto the top of this stack. The pushed item is also + * returned. This is equivalent to calling add. + * + * @param item the item to be added + * @return the item just pushed + */ + public E push(final E item) { + add(item); + return item; + } + + /** + * Returns the one-based position of the distance from the top that the + * specified object exists on this stack, where the top-most element is + * considered to be at distance 1. If the object is not + * present on the stack, return -1 instead. The + * equals() method is used to compare to the items + * in this stack. + * + * @param object the object to be searched for + * @return the 1-based depth into the stack of the object, or -1 if not found + */ + public int search(final Object object) { + int i = size() - 1; // Current index + int n = 1; // Current distance + while (i >= 0) { + final Object current = get(i); + if ((object == null && current == null) || + (object != null && object.equals(current))) { + return n; + } + i--; + n++; + } + return -1; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/Bag.java b/fine-commons-collections4/src/org/apache/commons/collections4/Bag.java new file mode 100644 index 000000000..dd88ae79f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/Bag.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +/** + * Defines a collection that counts the number of times an object appears in + * the collection. + *

+ * Suppose you have a Bag that contains {a, a, b, c}. + * Calling {@link #getCount(Object)} on a would return 2, while + * calling {@link #uniqueSet()} would return {a, b, c}. + *

+ * NOTE: This interface violates the {@link Collection} contract. + * The behavior specified in many of these methods is not the same + * as the behavior specified by Collection. + * The noncompliant methods are clearly marked with "(Violation)". + * Exercise caution when using a bag as a Collection. + *

+ * This violation resulted from the original specification of this interface. + * In an ideal world, the interface would be changed to fix the problems, however + * it has been decided to maintain backwards compatibility instead. + * + * @param the type held in the bag + * @since 2.0 + * @version $Id: Bag.java 1477779 2013-04-30 18:55:24Z tn $ + */ +public interface Bag extends Collection { + + /** + * Returns the number of occurrences (cardinality) of the given + * object currently in the bag. If the object does not exist in the + * bag, return 0. + * + * @param object the object to search for + * @return the number of occurrences of the object, zero if not found + */ + int getCount(Object object); + + /** + * (Violation) + * Adds one copy of the specified object to the Bag. + *

+ * If the object is already in the {@link #uniqueSet()} then increment its + * count as reported by {@link #getCount(Object)}. Otherwise add it to the + * {@link #uniqueSet()} and report its count as 1. + *

+ * Since this method always increases the size of the bag, + * according to the {@link Collection#add(Object)} contract, it + * should always return true. Since it sometimes returns + * false, this method violates the contract. + * + * @param object the object to add + * @return true if the object was not already in the uniqueSet + */ + boolean add(E object); + + /** + * Adds nCopies copies of the specified object to the Bag. + *

+ * If the object is already in the {@link #uniqueSet()} then increment its + * count as reported by {@link #getCount(Object)}. Otherwise add it to the + * {@link #uniqueSet()} and report its count as nCopies. + * + * @param object the object to add + * @param nCopies the number of copies to add + * @return true if the object was not already in the uniqueSet + */ + boolean add(E object, int nCopies); + + /** + * (Violation) + * Removes all occurrences of the given object from the bag. + *

+ * This will also remove the object from the {@link #uniqueSet()}. + *

+ * According to the {@link Collection#remove(Object)} method, + * this method should only remove the first occurrence of the + * given object, not all occurrences. + * + * @param object the object to remove + * @return true if this call changed the collection + */ + boolean remove(Object object); + + /** + * Removes nCopies copies of the specified object from the Bag. + *

+ * If the number of copies to remove is greater than the actual number of + * copies in the Bag, no error is thrown. + * + * @param object the object to remove + * @param nCopies the number of copies to remove + * @return true if this call changed the collection + */ + boolean remove(Object object, int nCopies); + + /** + * Returns a {@link Set} of unique elements in the Bag. + *

+ * Uniqueness constraints are the same as those in {@link java.util.Set}. + * + * @return the Set of unique Bag elements + */ + Set uniqueSet(); + + /** + * Returns the total number of items in the bag across all types. + * + * @return the total size of the Bag + */ + int size(); + + /** + * (Violation) + * Returns true if the bag contains all elements in + * the given collection, respecting cardinality. That is, if the + * given collection coll contains n copies + * of a given object, calling {@link #getCount(Object)} on that object must + * be >= n for all n in coll. + *

+ * The {@link Collection#containsAll(Collection)} method specifies + * that cardinality should not be respected; this method should + * return true if the bag contains at least one of every object contained + * in the given collection. + * + * @param coll the collection to check against + * @return true if the Bag contains all the collection + */ + boolean containsAll(Collection coll); + + /** + * (Violation) + * Remove all elements represented in the given collection, + * respecting cardinality. That is, if the given collection + * coll contains n copies of a given object, + * the bag will have n fewer copies, assuming the bag + * had at least n copies to begin with. + * + *

The {@link Collection#removeAll(Collection)} method specifies + * that cardinality should not be respected; this method should + * remove all occurrences of every object contained in the + * given collection. + * + * @param coll the collection to remove + * @return true if this call changed the collection + */ + boolean removeAll(Collection coll); + + /** + * (Violation) + * Remove any members of the bag that are not in the given + * collection, respecting cardinality. That is, if the given + * collection coll contains n copies of a + * given object and the bag has m > n copies, then + * delete m - n copies from the bag. In addition, if + * e is an object in the bag but + * !coll.contains(e), then remove e and any + * of its copies. + * + *

The {@link Collection#retainAll(Collection)} method specifies + * that cardinality should not be respected; this method should + * keep all occurrences of every object contained in the + * given collection. + * + * @param coll the collection to retain + * @return true if this call changed the collection + */ + boolean retainAll(Collection coll); + + /** + * Returns an {@link Iterator} over the entire set of members, + * including copies due to cardinality. This iterator is fail-fast + * and will not tolerate concurrent modifications. + * + * @return iterator over all elements in the Bag + */ + Iterator iterator(); + + // The following is not part of the formal Bag interface, however where possible + // Bag implementations should follow these comments. +// /** +// * Compares this Bag to another. +// * This Bag equals another Bag if it contains the same number of occurrences of +// * the same elements. +// * This equals definition is compatible with the Set interface. +// * +// * @param obj the Bag to compare to +// * @return true if equal +// */ +// boolean equals(Object obj); +// +// /** +// * Gets a hash code for the Bag compatible with the definition of equals. +// * The hash code is defined as the sum total of a hash code for each element. +// * The per element hash code is defined as +// * (e==null ? 0 : e.hashCode()) ^ noOccurances). +// * This hash code definition is compatible with the Set interface. +// * +// * @return the hash code of the Bag +// */ +// int hashCode(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/BagUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/BagUtils.java new file mode 100644 index 000000000..236f808f8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/BagUtils.java @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import org.apache.commons.collections4.bag.CollectionBag; +import org.apache.commons.collections4.bag.HashBag; +import org.apache.commons.collections4.bag.PredicatedBag; +import org.apache.commons.collections4.bag.PredicatedSortedBag; +import org.apache.commons.collections4.bag.SynchronizedBag; +import org.apache.commons.collections4.bag.SynchronizedSortedBag; +import org.apache.commons.collections4.bag.TransformedBag; +import org.apache.commons.collections4.bag.TransformedSortedBag; +import org.apache.commons.collections4.bag.TreeBag; +import org.apache.commons.collections4.bag.UnmodifiableBag; +import org.apache.commons.collections4.bag.UnmodifiableSortedBag; + +/** + * Provides utility methods and decorators for {@link Bag} and {@link SortedBag} instances. + * + * @since 2.1 + * @version $Id: BagUtils.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class BagUtils { + + /** + * An empty unmodifiable bag. + */ + @SuppressWarnings("rawtypes") // OK, empty bag is compatible with any type + public static final Bag EMPTY_BAG = UnmodifiableBag.unmodifiableBag(new HashBag()); + + /** + * An empty unmodifiable sorted bag. + */ + @SuppressWarnings("rawtypes") // OK, empty bag is compatible with any type + public static final Bag EMPTY_SORTED_BAG = + UnmodifiableSortedBag.unmodifiableSortedBag(new TreeBag()); + + /** + * Instantiation of BagUtils is not intended or required. + */ + private BagUtils() {} + + //----------------------------------------------------------------------- + /** + * Returns a synchronized (thread-safe) bag backed by the given bag. In + * order to guarantee serial access, it is critical that all access to the + * backing bag is accomplished through the returned bag. + *

+ * It is imperative that the user manually synchronize on the returned bag + * when iterating over it: + * + *

+     * Bag bag = BagUtils.synchronizedBag(new HashBag());
+     * ...
+     * synchronized(bag) {
+     *     Iterator i = bag.iterator(); // Must be in synchronized block
+     *     while (i.hasNext())
+     *         foo(i.next());
+     *     }
+     * }
+     * 
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param the element type + * @param bag the bag to synchronize, must not be null + * @return a synchronized bag backed by that bag + * @throws NullPointerException if the Bag is null + */ + public static Bag synchronizedBag(final Bag bag) { + return SynchronizedBag.synchronizedBag(bag); + } + + /** + * Returns an unmodifiable view of the given bag. Any modification attempts + * to the returned bag will raise an {@link UnsupportedOperationException}. + * + * @param the element type + * @param bag the bag whose unmodifiable view is to be returned, must not be null + * @return an unmodifiable view of that bag + * @throws NullPointerException if the Bag is null + */ + public static Bag unmodifiableBag(final Bag bag) { + return UnmodifiableBag.unmodifiableBag(bag); + } + + /** + * Returns a predicated (validating) bag backed by the given bag. + *

+ * Only objects that pass the test in the given predicate can be added to + * the bag. Trying to add an invalid object results in an + * IllegalArgumentException. It is important not to use the original bag + * after invoking this method, as it is a backdoor for adding invalid + * objects. + * + * @param the element type + * @param bag the bag to predicate, must not be null + * @param predicate the predicate for the bag, must not be null + * @return a predicated bag backed by the given bag + * @throws NullPointerException if the Bag or Predicate is null + */ + public static Bag predicatedBag(final Bag bag, final Predicate predicate) { + return PredicatedBag.predicatedBag(bag, predicate); + } + + /** + * Returns a transformed bag backed by the given bag. + *

+ * Each object is passed through the transformer as it is added to the Bag. + * It is important not to use the original bag after invoking this method, + * as it is a backdoor for adding untransformed objects. + *

+ * Existing entries in the specified bag will not be transformed. + * If you want that behaviour, see {@link TransformedBag#transformedBag(Bag, Transformer)}. + * + * @param the element type + * @param bag the bag to predicate, must not be null + * @param transformer the transformer for the bag, must not be null + * @return a transformed bag backed by the given bag + * @throws NullPointerException if the Bag or Transformer is null + */ + public static Bag transformingBag(final Bag bag, final Transformer transformer) { + return TransformedBag.transformingBag(bag, transformer); + } + + /** + * Returns a bag that complies to the Collection contract, backed by the given bag. + * + * @param the element type + * @param bag the bag to decorate, must not be null + * @return a Bag that complies to the Collection contract + * @throws NullPointerException if bag is null + * @since 4.0 + */ + public static Bag collectionBag(final Bag bag) { + return CollectionBag.collectionBag(bag); + } + + //----------------------------------------------------------------------- + /** + * Returns a synchronized (thread-safe) sorted bag backed by the given + * sorted bag. In order to guarantee serial access, it is critical that all + * access to the backing bag is accomplished through the returned bag. + *

+ * It is imperative that the user manually synchronize on the returned bag + * when iterating over it: + * + *

+     * SortedBag bag = BagUtils.synchronizedSortedBag(new TreeBag());
+     * ...
+     * synchronized(bag) {
+     *     Iterator i = bag.iterator(); // Must be in synchronized block
+     *     while (i.hasNext())
+     *         foo(i.next());
+     *     }
+     * }
+     * 
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param the element type + * @param bag the bag to synchronize, must not be null + * @return a synchronized bag backed by that bag + * @throws NullPointerException if the SortedBag is null + */ + public static SortedBag synchronizedSortedBag(final SortedBag bag) { + return SynchronizedSortedBag.synchronizedSortedBag(bag); + } + + /** + * Returns an unmodifiable view of the given sorted bag. Any modification + * attempts to the returned bag will raise an + * {@link UnsupportedOperationException}. + * + * @param the element type + * @param bag the bag whose unmodifiable view is to be returned, must not be null + * @return an unmodifiable view of that bag + * @throws NullPointerException if the SortedBag is null + */ + public static SortedBag unmodifiableSortedBag(final SortedBag bag) { + return UnmodifiableSortedBag.unmodifiableSortedBag(bag); + } + + /** + * Returns a predicated (validating) sorted bag backed by the given sorted + * bag. + *

+ * Only objects that pass the test in the given predicate can be added to + * the bag. Trying to add an invalid object results in an + * IllegalArgumentException. It is important not to use the original bag + * after invoking this method, as it is a backdoor for adding invalid + * objects. + * + * @param the element type + * @param bag the sorted bag to predicate, must not be null + * @param predicate the predicate for the bag, must not be null + * @return a predicated bag backed by the given bag + * @throws NullPointerException if the SortedBag or Predicate is null + */ + public static SortedBag predicatedSortedBag(final SortedBag bag, + final Predicate predicate) { + return PredicatedSortedBag.predicatedSortedBag(bag, predicate); + } + + /** + * Returns a transformed sorted bag backed by the given bag. + *

+ * Each object is passed through the transformer as it is added to the Bag. + * It is important not to use the original bag after invoking this method, + * as it is a backdoor for adding untransformed objects. + *

+ * Existing entries in the specified bag will not be transformed. + * If you want that behaviour, see + * {@link TransformedSortedBag#transformedSortedBag(SortedBag, Transformer)}. + * + * @param the element type + * @param bag the bag to predicate, must not be null + * @param transformer the transformer for the bag, must not be null + * @return a transformed bag backed by the given bag + * @throws NullPointerException if the Bag or Transformer is null + */ + public static SortedBag transformingSortedBag(final SortedBag bag, + final Transformer transformer) { + return TransformedSortedBag.transformingSortedBag(bag, transformer); + } + + /** + * Get an empty Bag. + * + * @param the element type + * @return an empty Bag + */ + @SuppressWarnings("unchecked") // OK, empty bag is compatible with any type + public static Bag emptyBag() { + return (Bag) EMPTY_BAG; + } + + /** + * Get an empty SortedBag. + * + * @param the element type + * @return an empty sorted Bag + */ + @SuppressWarnings("unchecked") // OK, empty bag is compatible with any type + public static SortedBag emptySortedBag() { + return (SortedBag) EMPTY_SORTED_BAG; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/BidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/BidiMap.java new file mode 100644 index 000000000..1bb252a23 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/BidiMap.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Set; + +/** + * Defines a map that allows bidirectional lookup between key and values. + *

+ * This extended Map represents a mapping where a key may + * lookup a value and a value may lookup a key with equal ease. + * This interface extends Map and so may be used anywhere a map + * is required. The interface provides an inverse map view, enabling + * full access to both directions of the BidiMap. + *

+ * Implementations should allow a value to be looked up from a key and + * a key to be looked up from a value with equal performance. + *

+ * This map enforces the restriction that there is a 1:1 relation between + * keys and values, meaning that multiple keys cannot map to the same value. + * This is required so that "inverting" the map results in a map without + * duplicate keys. See the {@link #put} method description for more information. + * + * @param the type of the keys in the map + * @param the type of the values in the map + * + * @since 3.0 + * @version $Id: BidiMap.java 1612021 2014-07-20 04:51:05Z ggregory $ + */ +public interface BidiMap extends IterableMap { + + /** + * Puts the key-value pair into the map, replacing any previous pair. + *

+ * When adding a key-value pair, the value may already exist in the map + * against a different key. That mapping is removed, to ensure that the + * value only occurs once in the inverse map. + *

+     *  BidiMap map1 = new DualHashBidiMap();
+     *  map.put("A","B");  // contains A mapped to B, as per Map
+     *  map.put("A","C");  // contains A mapped to C, as per Map
+     *
+     *  BidiMap map2 = new DualHashBidiMap();
+     *  map.put("A","B");  // contains A mapped to B, as per Map
+     *  map.put("C","B");  // contains C mapped to B, key A is removed
+     * 
+ * + * @param key the key to store + * @param value the value to store + * @return the previous value mapped to this key + * + * @throws UnsupportedOperationException if the put method is not supported + * @throws ClassCastException (optional) if the map limits the type of the + * value and the specified value is inappropriate + * @throws IllegalArgumentException (optional) if the map limits the values + * in some way and the value was invalid + * @throws NullPointerException (optional) if the map limits the values to + * non-null and null was specified + */ + V put(K key, V value); + + /** + * Gets the key that is currently mapped to the specified value. + *

+ * If the value is not contained in the map, null is returned. + *

+ * Implementations should seek to make this method perform equally as well + * as get(Object). + * + * @param value the value to find the key for + * @return the mapped key, or null if not found + * + * @throws ClassCastException (optional) if the map limits the type of the + * value and the specified value is inappropriate + * @throws NullPointerException (optional) if the map limits the values to + * non-null and null was specified + */ + K getKey(Object value); + + /** + * Removes the key-value pair that is currently mapped to the specified + * value (optional operation). + *

+ * If the value is not contained in the map, null is returned. + *

+ * Implementations should seek to make this method perform equally as well + * as remove(Object). + * + * @param value the value to find the key-value pair for + * @return the key that was removed, null if nothing removed + * + * @throws ClassCastException (optional) if the map limits the type of the + * value and the specified value is inappropriate + * @throws NullPointerException (optional) if the map limits the values to + * non-null and null was specified + * @throws UnsupportedOperationException if this method is not supported + * by the implementation + */ + K removeValue(Object value); + + /** + * Gets a view of this map where the keys and values are reversed. + *

+ * Changes to one map will be visible in the other and vice versa. + * This enables both directions of the map to be accessed as a Map. + *

+ * Implementations should seek to avoid creating a new object every time this + * method is called. See AbstractMap.values() etc. Calling this + * method on the inverse map should return the original. + * + * @return an inverted bidirectional map + */ + BidiMap inverseBidiMap(); + + /** + * Returns a {@link Set} view of the values contained in this map. + * The set is backed by the map, so changes to the map are reflected + * in the set, and vice-versa. If the map is modified while an iteration + * over the set is in progress (except through the iterator's own + * {@code remove} operation), the results of the iteration are undefined. + * The set supports element removal, which removes the corresponding + * mapping from the map, via the {@code Iterator.remove}, + * {@code Collection.remove}, {@code removeAll}, + * {@code retainAll} and {@code clear} operations. It does not + * support the {@code add} or {@code addAll} operations. + * + * @return a set view of the values contained in this map + */ + Set values(); +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/BoundedCollection.java b/fine-commons-collections4/src/org/apache/commons/collections4/BoundedCollection.java new file mode 100644 index 000000000..6e668ea65 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/BoundedCollection.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; + +/** + * Defines a collection that is bounded in size. + *

+ * The size of the collection can vary, but it can never exceed a preset + * maximum number of elements. This interface allows the querying of details + * associated with the maximum number of elements. + * + * @see CollectionUtils#isFull + * @see CollectionUtils#maxSize + * + * @since 3.0 + * @version $Id: BoundedCollection.java 1477779 2013-04-30 18:55:24Z tn $ + */ +public interface BoundedCollection extends Collection { + + /** + * Returns true if this collection is full and no new elements can be added. + * + * @return true if the collection is full + */ + boolean isFull(); + + /** + * Gets the maximum size of the collection (the bound). + * + * @return the maximum number of elements the collection can hold + */ + int maxSize(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/BoundedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/BoundedMap.java new file mode 100644 index 000000000..ce553aba6 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/BoundedMap.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Defines a map that is bounded in size. + *

+ * The size of the map can vary, but it can never exceed a preset + * maximum number of elements. This interface allows the querying of details + * associated with the maximum number of elements. + * + * @since 3.0 + * @version $Id: BoundedMap.java 1477779 2013-04-30 18:55:24Z tn $ + */ +public interface BoundedMap extends IterableMap { + + /** + * Returns true if this map is full and no new elements can be added. + * + * @return true if the map is full + */ + boolean isFull(); + + /** + * Gets the maximum size of the map (the bound). + * + * @return the maximum number of elements the map can hold + */ + int maxSize(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/Closure.java b/fine-commons-collections4/src/org/apache/commons/collections4/Closure.java new file mode 100644 index 000000000..b71ae127f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/Closure.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Defines a functor interface implemented by classes that do something. + *

+ * A Closure represents a block of code which is executed from + * inside some block, function or iteration. It operates an input object. + *

+ * Standard implementations of common closures are provided by + * {@link ClosureUtils}. These include method invocation and for/while loops. + * + * @param the type that the closure acts on + * @since 1.0 + * @version $Id: Closure.java 1543261 2013-11-19 00:47:34Z ggregory $ + */ +public interface Closure { + + /** + * Performs an action on the specified input object. + * + * @param input the input to execute on + * @throws ClassCastException (runtime) if the input is the wrong class + * @throws IllegalArgumentException (runtime) if the input is invalid + * @throws FunctorException (runtime) if any other error occurs + */ + void execute(T input); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/ClosureUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/ClosureUtils.java new file mode 100644 index 000000000..86b224dee --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/ClosureUtils.java @@ -0,0 +1,376 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; +import java.util.Map; + +import org.apache.commons.collections4.functors.ChainedClosure; +import org.apache.commons.collections4.functors.EqualPredicate; +import org.apache.commons.collections4.functors.ExceptionClosure; +import org.apache.commons.collections4.functors.ForClosure; +import org.apache.commons.collections4.functors.IfClosure; +import org.apache.commons.collections4.functors.InvokerTransformer; +import org.apache.commons.collections4.functors.NOPClosure; +import org.apache.commons.collections4.functors.SwitchClosure; +import org.apache.commons.collections4.functors.TransformerClosure; +import org.apache.commons.collections4.functors.WhileClosure; + +/** + * ClosureUtils provides reference implementations and utilities + * for the Closure functor interface. The supplied closures are: + *

    + *
  • Invoker - invokes a method on the input object + *
  • For - repeatedly calls a closure for a fixed number of times + *
  • While - repeatedly calls a closure while a predicate is true + *
  • Chained - chains two or more closures together + *
  • If - calls one closure or another based on a predicate + *
  • Switch - calls one closure based on one or more predicates + *
  • SwitchMap - calls one closure looked up from a Map + *
  • Transformer - wraps a Transformer as a Closure + *
  • NOP - does nothing + *
  • Exception - always throws an exception + *
+ *

+ * Since v4.1 only closures which are considered to be unsafe are + * Serializable. Closures considered to be unsafe for serialization are: + *

    + *
  • Invoker + *
  • For + *
  • While + *
+ * + * @since 3.0 + * @version $Id: ClosureUtils.java 1714362 2015-11-14 20:38:02Z tn $ + */ +public class ClosureUtils { + + /** + * This class is not normally instantiated. + */ + private ClosureUtils() {} + + /** + * Gets a Closure that always throws an exception. + * This could be useful during testing as a placeholder. + * + * @see org.apache.commons.collections4.functors.ExceptionClosure + * + * @param the type that the closure acts on + * @return the closure + */ + public static Closure exceptionClosure() { + return ExceptionClosure.exceptionClosure(); + } + + /** + * Gets a Closure that will do nothing. + * This could be useful during testing as a placeholder. + * + * @see org.apache.commons.collections4.functors.NOPClosure + * + * @param the type that the closure acts on + * @return the closure + */ + public static Closure nopClosure() { + return NOPClosure.nopClosure(); + } + + /** + * Creates a Closure that calls a Transformer each time it is called. + * The transformer will be called using the closure's input object. + * The transformer's result will be ignored. + * + * @see org.apache.commons.collections4.functors.TransformerClosure + * + * @param the type that the closure acts on + * @param transformer the transformer to run each time in the closure, null means nop + * @return the closure + */ + public static Closure asClosure(final Transformer transformer) { + return TransformerClosure.transformerClosure(transformer); + } + + /** + * Creates a Closure that will call the closure count times. + *

+ * A null closure or zero count returns the NOPClosure. + * + * @see org.apache.commons.collections4.functors.ForClosure + * + * @param the type that the closure acts on + * @param count the number of times to loop + * @param closure the closure to call repeatedly + * @return the for closure + */ + public static Closure forClosure(final int count, final Closure closure) { + return ForClosure.forClosure(count, closure); + } + + /** + * Creates a Closure that will call the closure repeatedly until the + * predicate returns false. + * + * @see org.apache.commons.collections4.functors.WhileClosure + * + * @param the type that the closure acts on + * @param predicate the predicate to use as an end of loop test, not null + * @param closure the closure to call repeatedly, not null + * @return the while closure + * @throws NullPointerException if either argument is null + */ + public static Closure whileClosure(final Predicate predicate, final Closure closure) { + return WhileClosure.whileClosure(predicate, closure, false); + } + + /** + * Creates a Closure that will call the closure once and then repeatedly + * until the predicate returns false. + * + * @see org.apache.commons.collections4.functors.WhileClosure + * + * @param the type that the closure acts on + * @param closure the closure to call repeatedly, not null + * @param predicate the predicate to use as an end of loop test, not null + * @return the do-while closure + * @throws NullPointerException if either argument is null + */ + public static Closure doWhileClosure(final Closure closure, + final Predicate predicate) { + return WhileClosure.whileClosure(predicate, closure, true); + } + + /** + * Creates a Closure that will invoke a specific method on the closure's + * input object by reflection. + * + * @see org.apache.commons.collections4.functors.InvokerTransformer + * @see org.apache.commons.collections4.functors.TransformerClosure + * + * @param the type that the closure acts on + * @param methodName the name of the method + * @return the invoker closure + * @throws NullPointerException if the method name is null + */ + public static Closure invokerClosure(final String methodName) { + // reuse transformer as it has caching - this is lazy really, should have inner class here + return asClosure(InvokerTransformer.invokerTransformer(methodName)); + } + + /** + * Creates a Closure that will invoke a specific method on the closure's + * input object by reflection. + * + * @see org.apache.commons.collections4.functors.InvokerTransformer + * @see org.apache.commons.collections4.functors.TransformerClosure + * + * @param the type that the closure acts on + * @param methodName the name of the method + * @param paramTypes the parameter types + * @param args the arguments + * @return the invoker closure + * @throws NullPointerException if the method name is null + * @throws IllegalArgumentException if the paramTypes and args don't match + */ + public static Closure invokerClosure(final String methodName, final Class[] paramTypes, + final Object[] args) { + // reuse transformer as it has caching - this is lazy really, should have inner class here + return asClosure(InvokerTransformer.invokerTransformer(methodName, paramTypes, args)); + } + + /** + * Create a new Closure that calls each closure in turn, passing the + * result into the next closure. + * + * @see org.apache.commons.collections4.functors.ChainedClosure + * + * @param the type that the closure acts on + * @param closures an array of closures to chain + * @return the chained closure + * @throws NullPointerException if the closures array is null + * @throws NullPointerException if any closure in the array is null + */ + public static Closure chainedClosure(final Closure... closures) { + return ChainedClosure.chainedClosure(closures); + } + + /** + * Create a new Closure that calls each closure in turn, passing the + * result into the next closure. The ordering is that of the iterator() + * method on the collection. + * + * @see org.apache.commons.collections4.functors.ChainedClosure + * + * @param the type that the closure acts on + * @param closures a collection of closures to chain + * @return the chained closure + * @throws NullPointerException if the closures collection is null + * @throws NullPointerException if any closure in the collection is null + * @throws IllegalArgumentException if the closures collection is empty + */ + public static Closure chainedClosure(final Collection> closures) { + return ChainedClosure.chainedClosure(closures); + } + + /** + * Create a new Closure that calls another closure based on the + * result of the specified predicate. + * + * @see org.apache.commons.collections4.functors.IfClosure + * + * @param the type that the closure acts on + * @param predicate the validating predicate + * @param trueClosure the closure called if the predicate is true + * @return the if closure + * @throws NullPointerException if the predicate or closure is null + * @since 3.2 + */ + public static Closure ifClosure(final Predicate predicate, + final Closure trueClosure) { + return IfClosure.ifClosure(predicate, trueClosure); + } + + /** + * Create a new Closure that calls one of two closures depending + * on the specified predicate. + * + * @see org.apache.commons.collections4.functors.IfClosure + * + * @param the type that the closure acts on + * @param predicate the predicate to switch on + * @param trueClosure the closure called if the predicate is true + * @param falseClosure the closure called if the predicate is false + * @return the switch closure + * @throws NullPointerException if the predicate or either closure is null + */ + public static Closure ifClosure(final Predicate predicate, + final Closure trueClosure, + final Closure falseClosure) { + return IfClosure.ifClosure(predicate, trueClosure, falseClosure); + } + + /** + * Create a new Closure that calls one of the closures depending + * on the predicates. + *

+ * The closure at array location 0 is called if the predicate at array + * location 0 returned true. Each predicate is evaluated + * until one returns true. + * + * @see org.apache.commons.collections4.functors.SwitchClosure + * + * @param the type that the closure acts on + * @param predicates an array of predicates to check, not null + * @param closures an array of closures to call, not null + * @return the switch closure + * @throws NullPointerException if the either array is null + * @throws NullPointerException if any element in the arrays is null + * @throws IllegalArgumentException if the arrays have different sizes + */ + public static Closure switchClosure(final Predicate[] predicates, + final Closure[] closures) { + return SwitchClosure.switchClosure(predicates, closures, null); + } + + /** + * Create a new Closure that calls one of the closures depending + * on the predicates. + *

+ * The closure at array location 0 is called if the predicate at array + * location 0 returned true. Each predicate is evaluated + * until one returns true. If no predicates evaluate to true, the default + * closure is called. + * + * @see org.apache.commons.collections4.functors.SwitchClosure + * + * @param the type that the closure acts on + * @param predicates an array of predicates to check, not null + * @param closures an array of closures to call, not null + * @param defaultClosure the default to call if no predicate matches + * @return the switch closure + * @throws NullPointerException if the either array is null + * @throws NullPointerException if any element in the arrays is null + * @throws IllegalArgumentException if the arrays are different sizes + */ + public static Closure switchClosure(final Predicate[] predicates, + final Closure[] closures, + final Closure defaultClosure) { + return SwitchClosure.switchClosure(predicates, closures, defaultClosure); + } + + /** + * Create a new Closure that calls one of the closures depending + * on the predicates. + *

+ * The Map consists of Predicate keys and Closure values. A closure + * is called if its matching predicate returns true. Each predicate is evaluated + * until one returns true. If no predicates evaluate to true, the default + * closure is called. The default closure is set in the map with a + * null key. The ordering is that of the iterator() method on the entryset + * collection of the map. + * + * @see org.apache.commons.collections4.functors.SwitchClosure + * + * @param the type that the closure acts on + * @param predicatesAndClosures a map of predicates to closures + * @return the switch closure + * @throws NullPointerException if the map is null + * @throws NullPointerException if any closure in the map is null + * @throws IllegalArgumentException if the map is empty + * @throws ClassCastException if the map elements are of the wrong type + */ + public static Closure switchClosure(final Map, Closure> predicatesAndClosures) { + return SwitchClosure.switchClosure(predicatesAndClosures); + } + + /** + * Create a new Closure that uses the input object as a key to find the + * closure to call. + *

+ * The Map consists of object keys and Closure values. A closure + * is called if the input object equals the key. If there is no match, the + * default closure is called. The default closure is set in the map + * using a null key. + * + * @see org.apache.commons.collections4.functors.SwitchClosure + * + * @param the type that the closure acts on + * @param objectsAndClosures a map of objects to closures + * @return the closure + * @throws NullPointerException if the map is null + * @throws NullPointerException if any closure in the map is null + * @throws IllegalArgumentException if the map is empty + */ + @SuppressWarnings("unchecked") + public static Closure switchMapClosure(final Map> objectsAndClosures) { + if (objectsAndClosures == null) { + throw new NullPointerException("The object and closure map must not be null"); + } + final Closure def = objectsAndClosures.remove(null); + final int size = objectsAndClosures.size(); + final Closure[] trs = new Closure[size]; + final Predicate[] preds = new Predicate[size]; + int i = 0; + for (final Map.Entry> entry : objectsAndClosures.entrySet()) { + preds[i] = EqualPredicate.equalPredicate(entry.getKey()); + trs[i] = entry.getValue(); + i++; + } + return ClosureUtils.switchClosure(preds, trs, def); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/CollectionUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/CollectionUtils.java new file mode 100644 index 000000000..7575ea7e2 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/CollectionUtils.java @@ -0,0 +1,1892 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.bag.HashBag; +import org.apache.commons.collections4.collection.PredicatedCollection; +import org.apache.commons.collections4.collection.SynchronizedCollection; +import org.apache.commons.collections4.collection.TransformedCollection; +import org.apache.commons.collections4.collection.UnmodifiableBoundedCollection; +import org.apache.commons.collections4.collection.UnmodifiableCollection; +import org.apache.commons.collections4.functors.TruePredicate; +import org.apache.commons.collections4.iterators.CollatingIterator; +import org.apache.commons.collections4.iterators.PermutationIterator; + +/** + * Provides utility methods and decorators for {@link Collection} instances. + *

+ * Various utility methods might put the input objects into a Set/Map/Bag. In case + * the input objects override {@link Object#equals(Object)}, it is mandatory that + * the general contract of the {@link Object#hashCode()} method is maintained. + *

+ * NOTE: From 4.0, method parameters will take {@link Iterable} objects when possible. + * + * @since 1.0 + * @version $Id: CollectionUtils.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class CollectionUtils { + + /** + * Helper class to easily access cardinality properties of two collections. + * @param the element type + */ + private static class CardinalityHelper { + + /** Contains the cardinality for each object in collection A. */ + final Map cardinalityA; + + /** Contains the cardinality for each object in collection B. */ + final Map cardinalityB; + + /** + * Create a new CardinalityHelper for two collections. + * @param a the first collection + * @param b the second collection + */ + public CardinalityHelper(final Iterable a, final Iterable b) { + cardinalityA = CollectionUtils.getCardinalityMap(a); + cardinalityB = CollectionUtils.getCardinalityMap(b); + } + + /** + * Returns the maximum frequency of an object. + * @param obj the object + * @return the maximum frequency of the object + */ + public final int max(final Object obj) { + return Math.max(freqA(obj), freqB(obj)); + } + + /** + * Returns the minimum frequency of an object. + * @param obj the object + * @return the minimum frequency of the object + */ + public final int min(final Object obj) { + return Math.min(freqA(obj), freqB(obj)); + } + + /** + * Returns the frequency of this object in collection A. + * @param obj the object + * @return the frequency of the object in collection A + */ + public int freqA(final Object obj) { + return getFreq(obj, cardinalityA); + } + + /** + * Returns the frequency of this object in collection B. + * @param obj the object + * @return the frequency of the object in collection B + */ + public int freqB(final Object obj) { + return getFreq(obj, cardinalityB); + } + + private final int getFreq(final Object obj, final Map freqMap) { + final Integer count = freqMap.get(obj); + if (count != null) { + return count.intValue(); + } + return 0; + } + } + + /** + * Helper class for set-related operations, e.g. union, subtract, intersection. + * @param the element type + */ + private static class SetOperationCardinalityHelper extends CardinalityHelper implements Iterable { + + /** Contains the unique elements of the two collections. */ + private final Set elements; + + /** Output collection. */ + private final List newList; + + /** + * Create a new set operation helper from the two collections. + * @param a the first collection + * @param b the second collection + */ + public SetOperationCardinalityHelper(final Iterable a, final Iterable b) { + super(a, b); + elements = new HashSet(); + addAll(elements, a); + addAll(elements, b); + // the resulting list must contain at least each unique element, but may grow + newList = new ArrayList(elements.size()); + } + + public Iterator iterator() { + return elements.iterator(); + } + + /** + * Add the object {@code count} times to the result collection. + * @param obj the object to add + * @param count the count + */ + public void setCardinality(final O obj, final int count) { + for (int i = 0; i < count; i++) { + newList.add(obj); + } + } + + /** + * Returns the resulting collection. + * @return the result + */ + public Collection list() { + return newList; + } + + } + + /** + * An empty unmodifiable collection. + * The JDK provides empty Set and List implementations which could be used for + * this purpose. However they could be cast to Set or List which might be + * undesirable. This implementation only implements Collection. + */ + @SuppressWarnings("rawtypes") // we deliberately use the raw type here + public static final Collection EMPTY_COLLECTION = + UnmodifiableCollection.unmodifiableCollection(new ArrayList()); + + /** + * CollectionUtils should not normally be instantiated. + */ + private CollectionUtils() {} + + /** + * Returns the immutable EMPTY_COLLECTION with generic type safety. + * + * @see #EMPTY_COLLECTION + * @since 4.0 + * @param the element type + * @return immutable empty collection + */ + @SuppressWarnings("unchecked") // OK, empty collection is compatible with any type + public static Collection emptyCollection() { + return EMPTY_COLLECTION; + } + + /** + * Returns an immutable empty collection if the argument is null, + * or the argument itself otherwise. + * + * @param the element type + * @param collection the collection, possibly null + * @return an empty collection if the argument is null + */ + @SuppressWarnings("unchecked") // OK, empty collection is compatible with any type + public static Collection emptyIfNull(final Collection collection) { + return collection == null ? EMPTY_COLLECTION : collection; + } + + /** + * Returns a {@link Collection} containing the union of the given + * {@link Iterable}s. + *

+ * The cardinality of each element in the returned {@link Collection} will + * be equal to the maximum of the cardinality of that element in the two + * given {@link Iterable}s. + * + * @param a the first collection, must not be null + * @param b the second collection, must not be null + * @param the generic type that is able to represent the types contained + * in both input collections. + * @return the union of the two collections + * @see Collection#addAll + */ + public static Collection union(final Iterable a, final Iterable b) { + final SetOperationCardinalityHelper helper = new SetOperationCardinalityHelper(a, b); + for (final O obj : helper) { + helper.setCardinality(obj, helper.max(obj)); + } + return helper.list(); + } + + /** + * Returns a {@link Collection} containing the intersection of the given + * {@link Iterable}s. + *

+ * The cardinality of each element in the returned {@link Collection} will + * be equal to the minimum of the cardinality of that element in the two + * given {@link Iterable}s. + * + * @param a the first collection, must not be null + * @param b the second collection, must not be null + * @param the generic type that is able to represent the types contained + * in both input collections. + * @return the intersection of the two collections + * @see Collection#retainAll + * @see #containsAny + */ + public static Collection intersection(final Iterable a, final Iterable b) { + final SetOperationCardinalityHelper helper = new SetOperationCardinalityHelper(a, b); + for (final O obj : helper) { + helper.setCardinality(obj, helper.min(obj)); + } + return helper.list(); + } + + /** + * Returns a {@link Collection} containing the exclusive disjunction + * (symmetric difference) of the given {@link Iterable}s. + *

+ * The cardinality of each element e in the returned + * {@link Collection} will be equal to + * max(cardinality(e,a),cardinality(e,b)) - min(cardinality(e,a), + * cardinality(e,b)). + *

+ * This is equivalent to + * {@code {@link #subtract subtract}({@link #union union(a,b)},{@link #intersection intersection(a,b)})} + * or + * {@code {@link #union union}({@link #subtract subtract(a,b)},{@link #subtract subtract(b,a)})}. + + * @param a the first collection, must not be null + * @param b the second collection, must not be null + * @param the generic type that is able to represent the types contained + * in both input collections. + * @return the symmetric difference of the two collections + */ + public static Collection disjunction(final Iterable a, final Iterable b) { + final SetOperationCardinalityHelper helper = new SetOperationCardinalityHelper(a, b); + for (final O obj : helper) { + helper.setCardinality(obj, helper.max(obj) - helper.min(obj)); + } + return helper.list(); + } + + /** + * Returns a new {@link Collection} containing {@code a - b}. + * The cardinality of each element e in the returned {@link Collection} + * will be the cardinality of e in a minus the cardinality + * of e in b, or zero, whichever is greater. + * + * @param a the collection to subtract from, must not be null + * @param b the collection to subtract, must not be null + * @param the generic type that is able to represent the types contained + * in both input collections. + * @return a new collection with the results + * @see Collection#removeAll + */ + public static Collection subtract(final Iterable a, final Iterable b) { + final Predicate p = TruePredicate.truePredicate(); + return subtract(a, b, p); + } + + /** + * Returns a new {@link Collection} containing a minus a subset of + * b. Only the elements of b that satisfy the predicate + * condition, p are subtracted from a. + * + *

The cardinality of each element e in the returned {@link Collection} + * that satisfies the predicate condition will be the cardinality of e in a + * minus the cardinality of e in b, or zero, whichever is greater.

+ *

The cardinality of each element e in the returned {@link Collection} that does not + * satisfy the predicate condition will be equal to the cardinality of e in a.

+ * + * @param a the collection to subtract from, must not be null + * @param b the collection to subtract, must not be null + * @param p the condition used to determine which elements of b are + * subtracted. + * @param the generic type that is able to represent the types contained + * in both input collections. + * @return a new collection with the results + * @since 4.0 + * @see Collection#removeAll + */ + public static Collection subtract(final Iterable a, + final Iterable b, + final Predicate p) { + final ArrayList list = new ArrayList(); + final HashBag bag = new HashBag(); + for (final O element : b) { + if (p.evaluate(element)) { + bag.add(element); + } + } + for (final O element : a) { + if (!bag.remove(element, 1)) { + list.add(element); + } + } + return list; + } + + /** + * Returns true iff all elements of {@code coll2} are also contained + * in {@code coll1}. The cardinality of values in {@code coll2} is not taken into account, + * which is the same behavior as {@link Collection#containsAll(Collection)}. + *

+ * In other words, this method returns true iff the + * {@link #intersection} of coll1 and coll2 has the same cardinality as + * the set of unique values from {@code coll2}. In case {@code coll2} is empty, {@code true} + * will be returned. + *

+ * This method is intended as a replacement for {@link Collection#containsAll(Collection)} + * with a guaranteed runtime complexity of {@code O(n + m)}. Depending on the type of + * {@link Collection} provided, this method will be much faster than calling + * {@link Collection#containsAll(Collection)} instead, though this will come at the + * cost of an additional space complexity O(n). + * + * @param coll1 the first collection, must not be null + * @param coll2 the second collection, must not be null + * @return true iff the intersection of the collections has the same cardinality + * as the set of unique elements from the second collection + * @since 4.0 + */ + public static boolean containsAll(final Collection coll1, final Collection coll2) { + if (coll2.isEmpty()) { + return true; + } else { + final Iterator it = coll1.iterator(); + final Set elementsAlreadySeen = new HashSet(); + for (final Object nextElement : coll2) { + if (elementsAlreadySeen.contains(nextElement)) { + continue; + } + + boolean foundCurrentElement = false; + while (it.hasNext()) { + final Object p = it.next(); + elementsAlreadySeen.add(p); + if (nextElement == null ? p == null : nextElement.equals(p)) { + foundCurrentElement = true; + break; + } + } + + if (foundCurrentElement) { + continue; + } else { + return false; + } + } + return true; + } + } + + /** + * Returns true iff at least one element is in both collections. + *

+ * In other words, this method returns true iff the + * {@link #intersection} of coll1 and coll2 is not empty. + * + * @param coll1 the first collection, must not be null + * @param coll2 the second collection, must not be null + * @return true iff the intersection of the collections is non-empty + * @since 2.1 + * @see #intersection + */ + public static boolean containsAny(final Collection coll1, final Collection coll2) { + if (coll1.size() < coll2.size()) { + for (final Object aColl1 : coll1) { + if (coll2.contains(aColl1)) { + return true; + } + } + } else { + for (final Object aColl2 : coll2) { + if (coll1.contains(aColl2)) { + return true; + } + } + } + return false; + } + + /** + * Returns a {@link Map} mapping each unique element in the given + * {@link Collection} to an {@link Integer} representing the number + * of occurrences of that element in the {@link Collection}. + *

+ * Only those elements present in the collection will appear as + * keys in the map. + * + * @param the type of object in the returned {@link Map}. This is a super type of . + * @param coll the collection to get the cardinality map for, must not be null + * @return the populated cardinality map + */ + public static Map getCardinalityMap(final Iterable coll) { + final Map count = new HashMap(); + for (final O obj : coll) { + final Integer c = count.get(obj); + if (c == null) { + count.put(obj, Integer.valueOf(1)); + } else { + count.put(obj, Integer.valueOf(c.intValue() + 1)); + } + } + return count; + } + + /** + * Returns {@code true} iff a is a sub-collection of b, + * that is, iff the cardinality of e in a is less than or + * equal to the cardinality of e in b, for each element e + * in a. + * + * @param a the first (sub?) collection, must not be null + * @param b the second (super?) collection, must not be null + * @return true iff a is a sub-collection of b + * @see #isProperSubCollection + * @see Collection#containsAll + */ + public static boolean isSubCollection(final Collection a, final Collection b) { + final CardinalityHelper helper = new CardinalityHelper(a, b); + for (final Object obj : a) { + if (helper.freqA(obj) > helper.freqB(obj)) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} iff a is a proper sub-collection of b, + * that is, iff the cardinality of e in a is less + * than or equal to the cardinality of e in b, + * for each element e in a, and there is at least one + * element f such that the cardinality of f in b + * is strictly greater than the cardinality of f in a. + *

+ * The implementation assumes + *

    + *
  • a.size() and b.size() represent the + * total cardinality of a and b, resp.
  • + *
  • a.size() < Integer.MAXVALUE
  • + *
+ * + * @param a the first (sub?) collection, must not be null + * @param b the second (super?) collection, must not be null + * @return true iff a is a proper sub-collection of b + * @see #isSubCollection + * @see Collection#containsAll + */ + public static boolean isProperSubCollection(final Collection a, final Collection b) { + return a.size() < b.size() && CollectionUtils.isSubCollection(a, b); + } + + /** + * Returns {@code true} iff the given {@link Collection}s contain + * exactly the same elements with exactly the same cardinalities. + *

+ * That is, iff the cardinality of e in a is + * equal to the cardinality of e in b, + * for each element e in a or b. + * + * @param a the first collection, must not be null + * @param b the second collection, must not be null + * @return true iff the collections contain the same elements with the same cardinalities. + */ + public static boolean isEqualCollection(final Collection a, final Collection b) { + if(a.size() != b.size()) { + return false; + } + final CardinalityHelper helper = new CardinalityHelper(a, b); + if(helper.cardinalityA.size() != helper.cardinalityB.size()) { + return false; + } + for( final Object obj : helper.cardinalityA.keySet()) { + if(helper.freqA(obj) != helper.freqB(obj)) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} iff the given {@link Collection}s contain + * exactly the same elements with exactly the same cardinalities. + *

+ * That is, iff the cardinality of e in a is + * equal to the cardinality of e in b, + * for each element e in a or b. + *

+ * Note: from version 4.1 onwards this method requires the input + * collections and equator to be of compatible type (using bounded wildcards). + * Providing incompatible arguments (e.g. by casting to their rawtypes) + * will result in a {@code ClassCastException} thrown at runtime. + * + * @param the element type + * @param a the first collection, must not be null + * @param b the second collection, must not be null + * @param equator the Equator used for testing equality + * @return true iff the collections contain the same elements with the same cardinalities. + * @throws NullPointerException if the equator is null + * @since 4.0 + */ + public static boolean isEqualCollection(final Collection a, + final Collection b, + final Equator equator) { + if (equator == null) { + throw new NullPointerException("Equator must not be null."); + } + + if(a.size() != b.size()) { + return false; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + final Transformer transformer = new Transformer() { + public EquatorWrapper transform(final Object input) { + return new EquatorWrapper(equator, input); + } + }; + + return isEqualCollection(collect(a, transformer), collect(b, transformer)); + } + + /** + * Wraps another object and uses the provided Equator to implement + * {@link #equals(Object)} and {@link #hashCode()}. + *

+ * This class can be used to store objects into a Map. + * + * @param the element type + * @since 4.0 + */ + private static class EquatorWrapper { + private final Equator equator; + private final O object; + + public EquatorWrapper(final Equator equator, final O object) { + this.equator = equator; + this.object = object; + } + + public O getObject() { + return object; + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof EquatorWrapper)) { + return false; + } + @SuppressWarnings("unchecked") + final EquatorWrapper otherObj = (EquatorWrapper) obj; + return equator.equate(object, otherObj.getObject()); + } + + @Override + public int hashCode() { + return equator.hash(object); + } + } + + /** + * Returns the number of occurrences of obj in coll. + * + * @param obj the object to find the cardinality of + * @param coll the {@link Iterable} to search + * @param the type of object that the {@link Iterable} may contain. + * @return the the number of occurrences of obj in coll + * @throws NullPointerException if coll is null + * @deprecated since 4.1, use {@link IterableUtils#frequency(Iterable, Object)} instead. + * Be aware that the order of parameters has changed. + */ + @Deprecated + public static int cardinality(final O obj, final Iterable coll) { + if (coll == null) { + throw new NullPointerException("coll must not be null."); + } + return IterableUtils.frequency(coll, obj); + } + + /** + * Finds the first element in the given collection which matches the given predicate. + *

+ * If the input collection or predicate is null, or no element of the collection + * matches the predicate, null is returned. + * + * @param the type of object the {@link Iterable} contains + * @param collection the collection to search, may be null + * @param predicate the predicate to use, may be null + * @return the first element of the collection which matches the predicate or null if none could be found + * @deprecated since 4.1, use {@link IterableUtils#find(Iterable, Predicate)} instead + */ + @Deprecated + public static T find(final Iterable collection, final Predicate predicate) { + return predicate != null ? IterableUtils.find(collection, predicate) : null; + } + + /** + * Executes the given closure on each element in the collection. + *

+ * If the input collection or closure is null, there is no change made. + * + * @param the type of object the {@link Iterable} contains + * @param the closure type + * @param collection the collection to get the input from, may be null + * @param closure the closure to perform, may be null + * @return closure + * @deprecated since 4.1, use {@link IterableUtils#forEach(Iterable, Closure)} instead + */ + @Deprecated + public static > C forAllDo(final Iterable collection, final C closure) { + if (closure != null) { + IterableUtils.forEach(collection, closure); + } + return closure; + } + + /** + * Executes the given closure on each element in the collection. + *

+ * If the input collection or closure is null, there is no change made. + * + * @param the type of object the {@link Iterator} contains + * @param the closure type + * @param iterator the iterator to get the input from, may be null + * @param closure the closure to perform, may be null + * @return closure + * @since 4.0 + * @deprecated since 4.1, use {@link IteratorUtils#forEach(Iterator, Closure)} instead + */ + @Deprecated + public static > C forAllDo(final Iterator iterator, final C closure) { + if (closure != null) { + IteratorUtils.forEach(iterator, closure); + } + return closure; + } + + /** + * Executes the given closure on each but the last element in the collection. + *

+ * If the input collection or closure is null, there is no change made. + * + * @param the type of object the {@link Iterable} contains + * @param the closure type + * @param collection the collection to get the input from, may be null + * @param closure the closure to perform, may be null + * @return the last element in the collection, or null if either collection or closure is null + * @since 4.0 + * @deprecated since 4.1, use {@link IterableUtils#forEachButLast(Iterable, Closure)} instead + */ + @Deprecated + public static > T forAllButLastDo(final Iterable collection, + final C closure) { + return closure != null ? IterableUtils.forEachButLast(collection, closure) : null; + } + + /** + * Executes the given closure on each but the last element in the collection. + *

+ * If the input collection or closure is null, there is no change made. + * + * @param the type of object the {@link Collection} contains + * @param the closure type + * @param iterator the iterator to get the input from, may be null + * @param closure the closure to perform, may be null + * @return the last element in the collection, or null if either iterator or closure is null + * @since 4.0 + * @deprecated since 4.1, use {@link IteratorUtils#forEachButLast(Iterator, Closure)} instead + */ + @Deprecated + public static > T forAllButLastDo(final Iterator iterator, final C closure) { + return closure != null ? IteratorUtils.forEachButLast(iterator, closure) : null; + } + + /** + * Filter the collection by applying a Predicate to each element. If the + * predicate returns false, remove the element. + *

+ * If the input collection or predicate is null, there is no change made. + * + * @param the type of object the {@link Iterable} contains + * @param collection the collection to get the input from, may be null + * @param predicate the predicate to use as a filter, may be null + * @return true if the collection is modified by this call, false otherwise. + */ + public static boolean filter(final Iterable collection, final Predicate predicate) { + boolean result = false; + if (collection != null && predicate != null) { + for (final Iterator it = collection.iterator(); it.hasNext();) { + if (!predicate.evaluate(it.next())) { + it.remove(); + result = true; + } + } + } + return result; + } + + /** + * Filter the collection by applying a Predicate to each element. If the + * predicate returns true, remove the element. + *

+ * This is equivalent to

filter(collection, PredicateUtils.notPredicate(predicate))
+ * if predicate is != null. + *

+ * If the input collection or predicate is null, there is no change made. + * + * @param the type of object the {@link Iterable} contains + * @param collection the collection to get the input from, may be null + * @param predicate the predicate to use as a filter, may be null + * @return true if the collection is modified by this call, false otherwise. + */ + public static boolean filterInverse(final Iterable collection, final Predicate predicate) { + return filter(collection, predicate == null ? null : PredicateUtils.notPredicate(predicate)); + } + + /** + * Transform the collection by applying a Transformer to each element. + *

+ * If the input collection or transformer is null, there is no change made. + *

+ * This routine is best for Lists, for which set() is used to do the + * transformations "in place." For other Collections, clear() and addAll() + * are used to replace elements. + *

+ * If the input collection controls its input, such as a Set, and the + * Transformer creates duplicates (or are otherwise invalid), the collection + * may reduce in size due to calling this method. + * + * @param the type of object the {@link Collection} contains + * @param collection the {@link Collection} to get the input from, may be null + * @param transformer the transformer to perform, may be null + */ + public static void transform(final Collection collection, + final Transformer transformer) { + + if (collection != null && transformer != null) { + if (collection instanceof List) { + final List list = (List) collection; + for (final ListIterator it = list.listIterator(); it.hasNext();) { + it.set(transformer.transform(it.next())); + } + } else { + final Collection resultCollection = collect(collection, transformer); + collection.clear(); + collection.addAll(resultCollection); + } + } + } + + /** + * Counts the number of elements in the input collection that match the + * predicate. + *

+ * A null collection or predicate matches no elements. + * + * @param the type of object the {@link Iterable} contains + * @param input the {@link Iterable} to get the input from, may be null + * @param predicate the predicate to use, may be null + * @return the number of matches for the predicate in the collection + * @deprecated since 4.1, use {@link IterableUtils#countMatches(Iterable, Predicate)} instead + */ + @Deprecated + public static int countMatches(final Iterable input, final Predicate predicate) { + return predicate == null ? 0 : (int) IterableUtils.countMatches(input, predicate); + } + + /** + * Answers true if a predicate is true for at least one element of a + * collection. + *

+ * A null collection or predicate returns false. + * + * @param the type of object the {@link Iterable} contains + * @param input the {@link Iterable} to get the input from, may be null + * @param predicate the predicate to use, may be null + * @return true if at least one element of the collection matches the predicate + * @deprecated since 4.1, use {@link IterableUtils#matchesAny(Iterable, Predicate)} instead + */ + @Deprecated + public static boolean exists(final Iterable input, final Predicate predicate) { + return predicate == null ? false : IterableUtils.matchesAny(input, predicate); + } + + /** + * Answers true if a predicate is true for every element of a + * collection. + *

+ * A null predicate returns false.
+ * A null or empty collection returns true. + * + * @param the type of object the {@link Iterable} contains + * @param input the {@link Iterable} to get the input from, may be null + * @param predicate the predicate to use, may be null + * @return true if every element of the collection matches the predicate or if the + * collection is empty, false otherwise + * @since 4.0 + * @deprecated since 4.1, use {@link IterableUtils#matchesAll(Iterable, Predicate)} instead + */ + @Deprecated + public static boolean matchesAll(final Iterable input, final Predicate predicate) { + return predicate == null ? false : IterableUtils.matchesAll(input, predicate); + } + + /** + * Selects all elements from input collection which match the given + * predicate into an output collection. + *

+ * A null predicate matches no elements. + * + * @param the type of object the {@link Iterable} contains + * @param inputCollection the collection to get the input from, may not be null + * @param predicate the predicate to use, may be null + * @return the elements matching the predicate (new list) + * @throws NullPointerException if the input collection is null + */ + public static Collection select(final Iterable inputCollection, + final Predicate predicate) { + final Collection answer = inputCollection instanceof Collection ? + new ArrayList(((Collection) inputCollection).size()) : new ArrayList(); + return select(inputCollection, predicate, answer); + } + + /** + * Selects all elements from input collection which match the given + * predicate and adds them to outputCollection. + *

+ * If the input collection or predicate is null, there is no change to the + * output collection. + * + * @param the type of object the {@link Iterable} contains + * @param the type of the output {@link Collection} + * @param inputCollection the collection to get the input from, may be null + * @param predicate the predicate to use, may be null + * @param outputCollection the collection to output into, may not be null if the inputCollection + * and predicate or not null + * @return the outputCollection + */ + public static > R select(final Iterable inputCollection, + final Predicate predicate, final R outputCollection) { + + if (inputCollection != null && predicate != null) { + for (final O item : inputCollection) { + if (predicate.evaluate(item)) { + outputCollection.add(item); + } + } + } + return outputCollection; + } + + /** + * Selects all elements from inputCollection into an output and rejected collection, + * based on the evaluation of the given predicate. + *

+ * Elements matching the predicate are added to the outputCollection, + * all other elements are added to the rejectedCollection. + *

+ * If the input predicate is null, no elements are added to + * outputCollection or rejectedCollection. + *

+ * Note: calling the method is equivalent to the following code snippet: + *

+     *   select(inputCollection, predicate, outputCollection);
+     *   selectRejected(inputCollection, predicate, rejectedCollection);
+     * 
+ * + * @param the type of object the {@link Iterable} contains + * @param the type of the output {@link Collection} + * @param inputCollection the collection to get the input from, may be null + * @param predicate the predicate to use, may be null + * @param outputCollection the collection to output selected elements into, may not be null if the + * inputCollection and predicate are not null + * @param rejectedCollection the collection to output rejected elements into, may not be null if the + * inputCollection or predicate are not null + * @return the outputCollection + * @since 4.1 + */ + public static > R select(final Iterable inputCollection, + final Predicate predicate, R outputCollection, R rejectedCollection) { + + if (inputCollection != null && predicate != null) { + for (final O element : inputCollection) { + if (predicate.evaluate(element)) { + outputCollection.add(element); + } else { + rejectedCollection.add(element); + } + } + } + return outputCollection; + } + + /** + * Selects all elements from inputCollection which don't match the given + * predicate into an output collection. + *

+ * If the input predicate is null, the result is an empty + * list. + * + * @param the type of object the {@link Iterable} contains + * @param inputCollection the collection to get the input from, may not be null + * @param predicate the predicate to use, may be null + * @return the elements not matching the predicate (new list) + * @throws NullPointerException if the input collection is null + */ + public static Collection selectRejected(final Iterable inputCollection, + final Predicate predicate) { + final Collection answer = inputCollection instanceof Collection ? + new ArrayList(((Collection) inputCollection).size()) : new ArrayList(); + return selectRejected(inputCollection, predicate, answer); + } + + /** + * Selects all elements from inputCollection which don't match the given + * predicate and adds them to outputCollection. + *

+ * If the input predicate is null, no elements are added to + * outputCollection. + * + * @param the type of object the {@link Iterable} contains + * @param the type of the output {@link Collection} + * @param inputCollection the collection to get the input from, may be null + * @param predicate the predicate to use, may be null + * @param outputCollection the collection to output into, may not be null if the inputCollection + * and predicate or not null + * @return outputCollection + */ + public static > R selectRejected(final Iterable inputCollection, + final Predicate predicate, final R outputCollection) { + + if (inputCollection != null && predicate != null) { + for (final O item : inputCollection) { + if (!predicate.evaluate(item)) { + outputCollection.add(item); + } + } + } + return outputCollection; + } + + /** + * Returns a new Collection containing all elements of the input collection + * transformed by the given transformer. + *

+ * If the input collection or transformer is null, the result is an empty list. + * + * @param the type of object in the input collection + * @param the type of object in the output collection + * @param inputCollection the collection to get the input from, may not be null + * @param transformer the transformer to use, may be null + * @return the transformed result (new list) + * @throws NullPointerException if the input collection is null + */ + public static Collection collect(final Iterable inputCollection, + final Transformer transformer) { + final Collection answer = inputCollection instanceof Collection ? + new ArrayList(((Collection) inputCollection).size()) : new ArrayList(); + return collect(inputCollection, transformer, answer); + } + + /** + * Transforms all elements from the input iterator with the given transformer + * and adds them to the output collection. + *

+ * If the input iterator or transformer is null, the result is an empty list. + * + * @param the type of object in the input collection + * @param the type of object in the output collection + * @param inputIterator the iterator to get the input from, may be null + * @param transformer the transformer to use, may be null + * @return the transformed result (new list) + */ + public static Collection collect(final Iterator inputIterator, + final Transformer transformer) { + return collect(inputIterator, transformer, new ArrayList()); + } + + /** + * Transforms all elements from input collection with the given transformer + * and adds them to the output collection. + *

+ * If the input collection or transformer is null, there is no change to the + * output collection. + * + * @param the type of object in the input collection + * @param the type of object in the output collection + * @param the type of the output collection + * @param inputCollection the collection to get the input from, may be null + * @param transformer the transformer to use, may be null + * @param outputCollection the collection to output into, may not be null if inputCollection + * and transformer are not null + * @return the output collection with the transformed input added + * @throws NullPointerException if the outputCollection is null and both, inputCollection and + * transformer are not null + */ + public static > R collect(final Iterable inputCollection, + final Transformer transformer, final R outputCollection) { + if (inputCollection != null) { + return collect(inputCollection.iterator(), transformer, outputCollection); + } + return outputCollection; + } + + /** + * Transforms all elements from the input iterator with the given transformer + * and adds them to the output collection. + *

+ * If the input iterator or transformer is null, there is no change to the + * output collection. + * + * @param the type of object in the input collection + * @param the type of object in the output collection + * @param the type of the output collection + * @param inputIterator the iterator to get the input from, may be null + * @param transformer the transformer to use, may be null + * @param outputCollection the collection to output into, may not be null if inputIterator + * and transformer are not null + * @return the outputCollection with the transformed input added + * @throws NullPointerException if the output collection is null and both, inputIterator and + * transformer are not null + */ + public static > R collect(final Iterator inputIterator, + final Transformer transformer, final R outputCollection) { + if (inputIterator != null && transformer != null) { + while (inputIterator.hasNext()) { + final I item = inputIterator.next(); + final O value = transformer.transform(item); + outputCollection.add(value); + } + } + return outputCollection; + } + + //----------------------------------------------------------------------- + /** + * Adds an element to the collection unless the element is null. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection to add to, must not be null + * @param object the object to add, if null it will not be added + * @return true if the collection changed + * @throws NullPointerException if the collection is null + * @since 3.2 + */ + public static boolean addIgnoreNull(final Collection collection, final T object) { + if (collection == null) { + throw new NullPointerException("The collection must not be null"); + } + return object != null && collection.add(object); + } + + /** + * Adds all elements in the {@link Iterable} to the given collection. If the + * {@link Iterable} is a {@link Collection} then it is cast and will be + * added using {@link Collection#addAll(Collection)} instead of iterating. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection to add to, must not be null + * @param iterable the iterable of elements to add, must not be null + * @return a boolean indicating whether the collection has changed or not. + * @throws NullPointerException if the collection or iterator is null + */ + public static boolean addAll(final Collection collection, final Iterable iterable) { + if (iterable instanceof Collection) { + return collection.addAll((Collection) iterable); + } + return addAll(collection, iterable.iterator()); + } + + /** + * Adds all elements in the iteration to the given collection. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection to add to, must not be null + * @param iterator the iterator of elements to add, must not be null + * @return a boolean indicating whether the collection has changed or not. + * @throws NullPointerException if the collection or iterator is null + */ + public static boolean addAll(final Collection collection, final Iterator iterator) { + boolean changed = false; + while (iterator.hasNext()) { + changed |= collection.add(iterator.next()); + } + return changed; + } + + /** + * Adds all elements in the enumeration to the given collection. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection to add to, must not be null + * @param enumeration the enumeration of elements to add, must not be null + * @return {@code true} if the collections was changed, {@code false} otherwise + * @throws NullPointerException if the collection or enumeration is null + */ + public static boolean addAll(final Collection collection, final Enumeration enumeration) { + boolean changed = false; + while (enumeration.hasMoreElements()) { + changed |= collection.add(enumeration.nextElement()); + } + return changed; + } + + /** + * Adds all elements in the array to the given collection. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection to add to, must not be null + * @param elements the array of elements to add, must not be null + * @return {@code true} if the collection was changed, {@code false} otherwise + * @throws NullPointerException if the collection or array is null + */ + public static boolean addAll(final Collection collection, final C[] elements) { + boolean changed = false; + for (final C element : elements) { + changed |= collection.add(element); + } + return changed; + } + + /** + * Returns the index-th value in {@link Iterator}, throwing + * IndexOutOfBoundsException if there is no such element. + *

+ * The Iterator is advanced to index (or to the end, if + * index exceeds the number of entries) as a side effect of this method. + * + * @param iterator the iterator to get a value from + * @param index the index to get + * @param the type of object in the {@link Iterator} + * @return the object at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + * @throws IllegalArgumentException if the object type is invalid + * @deprecated since 4.1, use {@code IteratorUtils.get(Iterator, int)} instead + */ + @Deprecated + public static T get(final Iterator iterator, final int index) { + return IteratorUtils.get(iterator, index); + } + + /** + * Ensures an index is not negative. + * @param index the index to check. + * @throws IndexOutOfBoundsException if the index is negative. + */ + static void checkIndexBounds(final int index) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index cannot be negative: " + index); + } + } + + /** + * Returns the index-th value in the iterable's {@link Iterator}, throwing + * IndexOutOfBoundsException if there is no such element. + *

+ * If the {@link Iterable} is a {@link List}, then it will use {@link List#get(int)}. + * + * @param iterable the {@link Iterable} to get a value from + * @param index the index to get + * @param the type of object in the {@link Iterable}. + * @return the object at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + * @deprecated since 4.1, use {@code IterableUtils.get(Iterable, int)} instead + */ + @Deprecated + public static T get(final Iterable iterable, final int index) { + return IterableUtils.get(iterable, index); + } + + /** + * Returns the index-th value in object, throwing + * IndexOutOfBoundsException if there is no such element or + * IllegalArgumentException if object is not an + * instance of one of the supported types. + *

+ * The supported types, and associated semantics are: + *

    + *
  • Map -- the value returned is the Map.Entry in position + * index in the map's entrySet iterator, + * if there is such an entry.
  • + *
  • List -- this method is equivalent to the list's get method.
  • + *
  • Array -- the index-th array entry is returned, + * if there is such an entry; otherwise an IndexOutOfBoundsException + * is thrown.
  • + *
  • Collection -- the value returned is the index-th object + * returned by the collection's default iterator, if there is such an element.
  • + *
  • Iterator or Enumeration -- the value returned is the + * index-th object in the Iterator/Enumeration, if there + * is such an element. The Iterator/Enumeration is advanced to + * index (or to the end, if index exceeds the + * number of entries) as a side effect of this method.
  • + *
+ * + * @param object the object to get a value from + * @param index the index to get + * @return the object at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + * @throws IllegalArgumentException if the object type is invalid + */ + public static Object get(final Object object, final int index) { + int i = index; + if (i < 0) { + throw new IndexOutOfBoundsException("Index cannot be negative: " + i); + } + if (object instanceof Map) { + final Map map = (Map) object; + final Iterator iterator = map.entrySet().iterator(); + return IteratorUtils.get(iterator, i); + } else if (object instanceof Object[]) { + return ((Object[]) object)[i]; + } else if (object instanceof Iterator) { + final Iterator it = (Iterator) object; + return IteratorUtils.get(it, i); + } else if (object instanceof Iterable) { + final Iterable iterable = (Iterable) object; + return IterableUtils.get(iterable, i); + } else if (object instanceof Collection) { + final Iterator iterator = ((Collection) object).iterator(); + return IteratorUtils.get(iterator, i); + } else if (object instanceof Enumeration) { + final Enumeration it = (Enumeration) object; + return EnumerationUtils.get(it, i); + } else if (object == null) { + throw new IllegalArgumentException("Unsupported object type: null"); + } else { + try { + return Array.get(object, i); + } catch (final IllegalArgumentException ex) { + throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName()); + } + } + } + + /** + * Returns the index-th Map.Entry in the map's entrySet, + * throwing IndexOutOfBoundsException if there is no such element. + * + * @param the key type in the {@link Map} + * @param the key type in the {@link Map} + * @param map the object to get a value from + * @param index the index to get + * @return the object at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + */ + public static Map.Entry get(final Map map, final int index) { + checkIndexBounds(index); + return get(map.entrySet(), index); + } + + /** + * Gets the size of the collection/iterator specified. + *

+ * This method can handles objects as follows + *

    + *
  • Collection - the collection size + *
  • Map - the map size + *
  • Array - the array size + *
  • Iterator - the number of elements remaining in the iterator + *
  • Enumeration - the number of elements remaining in the enumeration + *
+ * + * @param object the object to get the size of, may be null + * @return the size of the specified collection or 0 if the object was null + * @throws IllegalArgumentException thrown if object is not recognised + * @since 3.1 + */ + public static int size(final Object object) { + if (object == null) { + return 0; + } + int total = 0; + if (object instanceof Map) { + total = ((Map) object).size(); + } else if (object instanceof Collection) { + total = ((Collection) object).size(); + } else if (object instanceof Iterable) { + total = IterableUtils.size((Iterable) object); + } else if (object instanceof Object[]) { + total = ((Object[]) object).length; + } else if (object instanceof Iterator) { + total = IteratorUtils.size((Iterator) object); + } else if (object instanceof Enumeration) { + final Enumeration it = (Enumeration) object; + while (it.hasMoreElements()) { + total++; + it.nextElement(); + } + } else { + try { + total = Array.getLength(object); + } catch (final IllegalArgumentException ex) { + throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName()); + } + } + return total; + } + + /** + * Checks if the specified collection/array/iterator is empty. + *

+ * This method can handles objects as follows + *

    + *
  • Collection - via collection isEmpty + *
  • Map - via map isEmpty + *
  • Array - using array size + *
  • Iterator - via hasNext + *
  • Enumeration - via hasMoreElements + *
+ *

+ * Note: This method is named to avoid clashing with + * {@link #isEmpty(Collection)}. + * + * @param object the object to get the size of, may be null + * @return true if empty or null + * @throws IllegalArgumentException thrown if object is not recognised + * @since 3.2 + */ + public static boolean sizeIsEmpty(final Object object) { + if (object == null) { + return true; + } else if (object instanceof Collection) { + return ((Collection) object).isEmpty(); + } else if (object instanceof Iterable) { + return IterableUtils.isEmpty((Iterable) object); + } else if (object instanceof Map) { + return ((Map) object).isEmpty(); + } else if (object instanceof Object[]) { + return ((Object[]) object).length == 0; + } else if (object instanceof Iterator) { + return ((Iterator) object).hasNext() == false; + } else if (object instanceof Enumeration) { + return ((Enumeration) object).hasMoreElements() == false; + } else { + try { + return Array.getLength(object) == 0; + } catch (final IllegalArgumentException ex) { + throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName()); + } + } + } + + //----------------------------------------------------------------------- + /** + * Null-safe check if the specified collection is empty. + *

+ * Null returns true. + * + * @param coll the collection to check, may be null + * @return true if empty or null + * @since 3.2 + */ + public static boolean isEmpty(final Collection coll) { + return coll == null || coll.isEmpty(); + } + + /** + * Null-safe check if the specified collection is not empty. + *

+ * Null returns false. + * + * @param coll the collection to check, may be null + * @return true if non-null and non-empty + * @since 3.2 + */ + public static boolean isNotEmpty(final Collection coll) { + return !isEmpty(coll); + } + + //----------------------------------------------------------------------- + /** + * Reverses the order of the given array. + * + * @param array the array to reverse + */ + public static void reverseArray(final Object[] array) { + int i = 0; + int j = array.length - 1; + Object tmp; + + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + /** + * Returns true if no more elements can be added to the Collection. + *

+ * This method uses the {@link BoundedCollection} interface to determine the + * full status. If the collection does not implement this interface then + * false is returned. + *

+ * The collection does not have to implement this interface directly. + * If the collection has been decorated using the decorators subpackage + * then these will be removed to access the BoundedCollection. + * + * @param coll the collection to check + * @return true if the BoundedCollection is full + * @throws NullPointerException if the collection is null + */ + public static boolean isFull(final Collection coll) { + if (coll == null) { + throw new NullPointerException("The collection must not be null"); + } + if (coll instanceof BoundedCollection) { + return ((BoundedCollection) coll).isFull(); + } + try { + final BoundedCollection bcoll = + UnmodifiableBoundedCollection.unmodifiableBoundedCollection(coll); + return bcoll.isFull(); + } catch (final IllegalArgumentException ex) { + return false; + } + } + + /** + * Get the maximum number of elements that the Collection can contain. + *

+ * This method uses the {@link BoundedCollection} interface to determine the + * maximum size. If the collection does not implement this interface then + * -1 is returned. + *

+ * The collection does not have to implement this interface directly. + * If the collection has been decorated using the decorators subpackage + * then these will be removed to access the BoundedCollection. + * + * @param coll the collection to check + * @return the maximum size of the BoundedCollection, -1 if no maximum size + * @throws NullPointerException if the collection is null + */ + public static int maxSize(final Collection coll) { + if (coll == null) { + throw new NullPointerException("The collection must not be null"); + } + if (coll instanceof BoundedCollection) { + return ((BoundedCollection) coll).maxSize(); + } + try { + final BoundedCollection bcoll = + UnmodifiableBoundedCollection.unmodifiableBoundedCollection(coll); + return bcoll.maxSize(); + } catch (final IllegalArgumentException ex) { + return -1; + } + } + + //----------------------------------------------------------------------- + /** + * Merges two sorted Collections, a and b, into a single, sorted List + * such that the natural ordering of the elements is retained. + *

+ * Uses the standard O(n) merge algorithm for combining two sorted lists. + * + * @param the element type + * @param a the first collection, must not be null + * @param b the second collection, must not be null + * @return a new sorted List, containing the elements of Collection a and b + * @throws NullPointerException if either collection is null + * @since 4.0 + */ + public static > List collate(Iterable a, + Iterable b) { + return collate(a, b, ComparatorUtils.naturalComparator(), true); + } + + /** + * Merges two sorted Collections, a and b, into a single, sorted List + * such that the natural ordering of the elements is retained. + *

+ * Uses the standard O(n) merge algorithm for combining two sorted lists. + * + * @param the element type + * @param a the first collection, must not be null + * @param b the second collection, must not be null + * @param includeDuplicates if {@code true} duplicate elements will be retained, otherwise + * they will be removed in the output collection + * @return a new sorted List, containing the elements of Collection a and b + * @throws NullPointerException if either collection is null + * @since 4.0 + */ + public static > List collate(final Iterable a, + final Iterable b, + final boolean includeDuplicates) { + return collate(a, b, ComparatorUtils.naturalComparator(), includeDuplicates); + } + + /** + * Merges two sorted Collections, a and b, into a single, sorted List + * such that the ordering of the elements according to Comparator c is retained. + *

+ * Uses the standard O(n) merge algorithm for combining two sorted lists. + * + * @param the element type + * @param a the first collection, must not be null + * @param b the second collection, must not be null + * @param c the comparator to use for the merge. + * @return a new sorted List, containing the elements of Collection a and b + * @throws NullPointerException if either collection or the comparator is null + * @since 4.0 + */ + public static List collate(final Iterable a, final Iterable b, + final Comparator c) { + return collate(a, b, c, true); + } + + /** + * Merges two sorted Collections, a and b, into a single, sorted List + * such that the ordering of the elements according to Comparator c is retained. + *

+ * Uses the standard O(n) merge algorithm for combining two sorted lists. + * + * @param the element type + * @param a the first collection, must not be null + * @param b the second collection, must not be null + * @param c the comparator to use for the merge. + * @param includeDuplicates if {@code true} duplicate elements will be retained, otherwise + * they will be removed in the output collection + * @return a new sorted List, containing the elements of Collection a and b + * @throws NullPointerException if either collection or the comparator is null + * @since 4.0 + */ + public static List collate(final Iterable a, final Iterable b, + final Comparator c, final boolean includeDuplicates) { + + if (a == null || b == null) { + throw new NullPointerException("The collections must not be null"); + } + if (c == null) { + throw new NullPointerException("The comparator must not be null"); + } + + // if both Iterables are a Collection, we can estimate the size + final int totalSize = a instanceof Collection && b instanceof Collection ? + Math.max(1, ((Collection) a).size() + ((Collection) b).size()) : 10; + + final Iterator iterator = new CollatingIterator(c, a.iterator(), b.iterator()); + if (includeDuplicates) { + return IteratorUtils.toList(iterator, totalSize); + } else { + final ArrayList mergedList = new ArrayList(totalSize); + + O lastItem = null; + while (iterator.hasNext()) { + final O item = iterator.next(); + if (lastItem == null || !lastItem.equals(item)) { + mergedList.add(item); + } + lastItem = item; + } + + mergedList.trimToSize(); + return mergedList; + } + } + + //----------------------------------------------------------------------- + + /** + * Returns a {@link Collection} of all the permutations of the input collection. + *

+ * NOTE: the number of permutations of a given collection is equal to n!, where + * n is the size of the collection. Thus, the resulting collection will become + * very large for collections > 10 (e.g. 10! = 3628800, 15! = 1307674368000). + *

+ * For larger collections it is advised to use a {@link PermutationIterator} to + * iterate over all permutations. + * + * @see PermutationIterator + * + * @param the element type + * @param collection the collection to create permutations for, may not be null + * @return an unordered collection of all permutations of the input collection + * @throws NullPointerException if collection is null + * @since 4.0 + */ + public static Collection> permutations(final Collection collection) { + final PermutationIterator it = new PermutationIterator(collection); + final Collection> result = new LinkedList>(); + while (it.hasNext()) { + result.add(it.next()); + } + return result; + } + + //----------------------------------------------------------------------- + /** + * Returns a collection containing all the elements in collection + * that are also in retain. The cardinality of an element e + * in the returned collection is the same as the cardinality of e + * in collection unless retain does not contain e, in which + * case the cardinality is zero. This method is useful if you do not wish to modify + * the collection c and thus cannot call c.retainAll(retain);. + *

+ * This implementation iterates over collection, checking each element in + * turn to see if it's contained in retain. If it's contained, it's added + * to the returned list. As a consequence, it is advised to use a collection type for + * retain that provides a fast (e.g. O(1)) implementation of + * {@link Collection#contains(Object)}. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection whose contents are the target of the #retailAll operation + * @param retain the collection containing the elements to be retained in the returned collection + * @return a Collection containing all the elements of collection + * that occur at least once in retain. + * @throws NullPointerException if either parameter is null + * @since 3.2 + */ + public static Collection retainAll(final Collection collection, final Collection retain) { + return ListUtils.retainAll(collection, retain); + } + + /** + * Returns a collection containing all the elements in + * collection that are also in retain. The + * cardinality of an element e in the returned collection is + * the same as the cardinality of e in collection + * unless retain does not contain e, in which case + * the cardinality is zero. This method is useful if you do not wish to + * modify the collection c and thus cannot call + * c.retainAll(retain);. + *

+ * Moreover this method uses an {@link Equator} instead of + * {@link Object#equals(Object)} to determine the equality of the elements + * in collection and retain. Hence this method is + * useful in cases where the equals behavior of an object needs to be + * modified without changing the object itself. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection whose contents are the target of the {@code retainAll} operation + * @param retain the collection containing the elements to be retained in the returned collection + * @param equator the Equator used for testing equality + * @return a Collection containing all the elements of collection + * that occur at least once in retain according to the equator + * @throws NullPointerException if any of the parameters is null + * @since 4.1 + */ + public static Collection retainAll(final Iterable collection, + final Iterable retain, + final Equator equator) { + + final Transformer> transformer = new Transformer>() { + public EquatorWrapper transform(E input) { + return new EquatorWrapper(equator, input); + } + }; + + final Set> retainSet = + collect(retain, transformer, new HashSet>()); + + final List list = new ArrayList(); + for (final E element : collection) { + if (retainSet.contains(new EquatorWrapper(equator, element))) { + list.add(element); + } + } + return list; + } + + /** + * Removes the elements in remove from collection. That is, this + * method returns a collection containing all the elements in c + * that are not in remove. The cardinality of an element e + * in the returned collection is the same as the cardinality of e + * in collection unless remove contains e, in which + * case the cardinality is zero. This method is useful if you do not wish to modify + * the collection c and thus cannot call collection.removeAll(remove);. + *

+ * This implementation iterates over collection, checking each element in + * turn to see if it's contained in remove. If it's not contained, it's added + * to the returned list. As a consequence, it is advised to use a collection type for + * remove that provides a fast (e.g. O(1)) implementation of + * {@link Collection#contains(Object)}. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection from which items are removed (in the returned collection) + * @param remove the items to be removed from the returned collection + * @return a Collection containing all the elements of collection except + * any elements that also occur in remove. + * @throws NullPointerException if either parameter is null + * @since 4.0 (method existed in 3.2 but was completely broken) + */ + public static Collection removeAll(final Collection collection, final Collection remove) { + return ListUtils.removeAll(collection, remove); + } + + /** + * Removes all elements in remove from collection. + * That is, this method returns a collection containing all the elements in + * collection that are not in remove. The + * cardinality of an element e in the returned collection is + * the same as the cardinality of e in collection + * unless remove contains e, in which case the + * cardinality is zero. This method is useful if you do not wish to modify + * the collection c and thus cannot call + * collection.removeAll(remove). + *

+ * Moreover this method uses an {@link Equator} instead of + * {@link Object#equals(Object)} to determine the equality of the elements + * in collection and remove. Hence this method is + * useful in cases where the equals behavior of an object needs to be + * modified without changing the object itself. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection from which items are removed (in the returned collection) + * @param remove the items to be removed from the returned collection + * @param equator the Equator used for testing equality + * @return a Collection containing all the elements of collection + * except any element that if equal according to the equator + * @throws NullPointerException if any of the parameters is null + * @since 4.1 + */ + public static Collection removeAll(final Iterable collection, + final Iterable remove, + final Equator equator) { + + final Transformer> transformer = new Transformer>() { + public EquatorWrapper transform(E input) { + return new EquatorWrapper(equator, input); + } + }; + + final Set> removeSet = + collect(remove, transformer, new HashSet>()); + + final List list = new ArrayList(); + for (final E element : collection) { + if (!removeSet.contains(new EquatorWrapper(equator, element))) { + list.add(element); + } + } + return list; + } + + //----------------------------------------------------------------------- + /** + * Returns a synchronized collection backed by the given collection. + *

+ * You must manually synchronize on the returned buffer's iterator to + * avoid non-deterministic behavior: + * + *

+     * Collection c = CollectionUtils.synchronizedCollection(myCollection);
+     * synchronized (c) {
+     *     Iterator i = c.iterator();
+     *     while (i.hasNext()) {
+     *         process (i.next());
+     *     }
+     * }
+     * 
+ * + * This method uses the implementation in the decorators subpackage. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection to synchronize, must not be null + * @return a synchronized collection backed by the given collection + * @throws NullPointerException if the collection is null + * @deprecated since 4.1, use {@link java.util.Collections#synchronizedCollection(Collection)} instead + */ + @Deprecated + public static Collection synchronizedCollection(final Collection collection) { + return SynchronizedCollection.synchronizedCollection(collection); + } + + /** + * Returns an unmodifiable collection backed by the given collection. + *

+ * This method uses the implementation in the decorators subpackage. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection to make unmodifiable, must not be null + * @return an unmodifiable collection backed by the given collection + * @throws NullPointerException if the collection is null + * @deprecated since 4.1, use {@link java.util.Collections#unmodifiableCollection(Collection)} instead + */ + @Deprecated + public static Collection unmodifiableCollection(final Collection collection) { + return UnmodifiableCollection.unmodifiableCollection(collection); + } + + /** + * Returns a predicated (validating) collection backed by the given collection. + *

+ * Only objects that pass the test in the given predicate can be added to the collection. + * Trying to add an invalid object results in an IllegalArgumentException. + * It is important not to use the original collection after invoking this method, + * as it is a backdoor for adding invalid objects. + * + * @param the type of objects in the Collection. + * @param collection the collection to predicate, must not be null + * @param predicate the predicate for the collection, must not be null + * @return a predicated collection backed by the given collection + * @throws NullPointerException if the Collection is null + */ + public static Collection predicatedCollection(final Collection collection, + final Predicate predicate) { + return PredicatedCollection.predicatedCollection(collection, predicate); + } + + /** + * Returns a transformed bag backed by the given collection. + *

+ * Each object is passed through the transformer as it is added to the + * Collection. It is important not to use the original collection after invoking this + * method, as it is a backdoor for adding untransformed objects. + *

+ * Existing entries in the specified collection will not be transformed. + * If you want that behaviour, see {@link TransformedCollection#transformedCollection}. + * + * @param the type of object the {@link Collection} contains + * @param collection the collection to predicate, must not be null + * @param transformer the transformer for the collection, must not be null + * @return a transformed collection backed by the given collection + * @throws NullPointerException if the Collection or Transformer is null + */ + public static Collection transformingCollection(final Collection collection, + final Transformer transformer) { + return TransformedCollection.transformingCollection(collection, transformer); + } + + /** + * Extract the lone element of the specified Collection. + * @param collection type + * @param collection to read + * @return sole member of collection + * @throws NullPointerException if collection is null + * @throws IllegalArgumentException if collection is empty or contains more than one element + * @since 4.0 + */ + public static E extractSingleton(final Collection collection) { + if (collection == null) { + throw new NullPointerException("Collection must not be null."); + } + if (collection.size() != 1) { + throw new IllegalArgumentException("Can extract singleton only when collection size == 1"); + } + return collection.iterator().next(); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/ComparatorUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/ComparatorUtils.java new file mode 100644 index 000000000..f70cc9090 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/ComparatorUtils.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; +import java.util.Comparator; + +import org.apache.commons.collections4.comparators.BooleanComparator; +import org.apache.commons.collections4.comparators.ComparableComparator; +import org.apache.commons.collections4.comparators.ComparatorChain; +import org.apache.commons.collections4.comparators.NullComparator; +import org.apache.commons.collections4.comparators.ReverseComparator; +import org.apache.commons.collections4.comparators.TransformingComparator; + +/** + * Provides convenient static utility methods for Comparator + * objects. + *

+ * Most of the functionality in this class can also be found in the + * comparators package. This class merely provides a + * convenient central place if you have use for more than one class + * in the comparators subpackage. + * + * @since 2.1 + * @version $Id: ComparatorUtils.java 1591832 2014-05-02 08:58:40Z tn $ + */ +public class ComparatorUtils { + + /** + * ComparatorUtils should not normally be instantiated. + */ + private ComparatorUtils() {} + + /** + * Comparator for natural sort order. + * + * @see ComparableComparator#comparableComparator() + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) // explicit type needed for Java 1.5 compilation + public static final Comparator NATURAL_COMPARATOR = ComparableComparator.comparableComparator(); + + /** + * Gets a comparator that uses the natural order of the objects. + * + * @param the object type to compare + * @return a comparator which uses natural order + */ + @SuppressWarnings("unchecked") + public static > Comparator naturalComparator() { + return NATURAL_COMPARATOR; + } + + /** + * Gets a comparator that compares using an array of {@link Comparator}s, applied + * in sequence until one returns not equal or the array is exhausted. + * + * @param the object type to compare + * @param comparators the comparators to use, not null or empty or containing nulls + * @return a {@link ComparatorChain} formed from the input comparators + * @throws NullPointerException if comparators array is null or contains a null + * @see ComparatorChain + */ + public static Comparator chainedComparator(final Comparator... comparators) { + final ComparatorChain chain = new ComparatorChain(); + for (final Comparator comparator : comparators) { + if (comparator == null) { + throw new NullPointerException("Comparator cannot be null"); + } + chain.addComparator(comparator); + } + return chain; + } + + /** + * Gets a comparator that compares using a collection of {@link Comparator}s, + * applied in (default iterator) sequence until one returns not equal or the + * collection is exhausted. + * + * @param the object type to compare + * @param comparators the comparators to use, not null or empty or containing nulls + * @return a {@link ComparatorChain} formed from the input comparators + * @throws NullPointerException if comparators collection is null or contains a null + * @throws ClassCastException if the comparators collection contains the wrong object type + * @see ComparatorChain + */ + @SuppressWarnings("unchecked") + public static Comparator chainedComparator(final Collection> comparators) { + return chainedComparator( + (Comparator[]) comparators.toArray(new Comparator[comparators.size()]) + ); + } + + /** + * Gets a comparator that reverses the order of the given comparator. + * + * @param the object type to compare + * @param comparator the comparator to reverse + * @return a comparator that reverses the order of the input comparator + * @see ReverseComparator + */ + public static Comparator reversedComparator(final Comparator comparator) { + return new ReverseComparator(comparator); + } + + /** + * Gets a Comparator that can sort Boolean objects. + *

+ * The parameter specifies whether true or false is sorted first. + *

+ * The comparator throws NullPointerException if a null value is compared. + * + * @param trueFirst when true, sort + * true {@link Boolean}s before + * false {@link Boolean}s. + * @return a comparator that sorts booleans + */ + public static Comparator booleanComparator(final boolean trueFirst) { + return BooleanComparator.booleanComparator(trueFirst); + } + + /** + * Gets a Comparator that controls the comparison of null values. + *

+ * The returned comparator will consider a null value to be less than + * any nonnull value, and equal to any other null value. Two nonnull + * values will be evaluated with the given comparator. + * + * @param the object type to compare + * @param comparator the comparator that wants to allow nulls + * @return a version of that comparator that allows nulls + * @see NullComparator + */ + @SuppressWarnings("unchecked") + public static Comparator nullLowComparator(Comparator comparator) { + if (comparator == null) { + comparator = NATURAL_COMPARATOR; + } + return new NullComparator(comparator, false); + } + + /** + * Gets a Comparator that controls the comparison of null values. + *

+ * The returned comparator will consider a null value to be greater than + * any nonnull value, and equal to any other null value. Two nonnull + * values will be evaluated with the given comparator. + * + * @param the object type to compare + * @param comparator the comparator that wants to allow nulls + * @return a version of that comparator that allows nulls + * @see NullComparator + */ + @SuppressWarnings("unchecked") + public static Comparator nullHighComparator(Comparator comparator) { + if (comparator == null) { + comparator = NATURAL_COMPARATOR; + } + return new NullComparator(comparator, true); + } + + /** + * Gets a Comparator that passes transformed objects to the given comparator. + *

+ * Objects passed to the returned comparator will first be transformed + * by the given transformer before they are compared by the given + * comparator. + * + * @param the input object type of the transformed comparator + * @param the object type of the decorated comparator + * @param comparator the sort order to use + * @param transformer the transformer to use + * @return a comparator that transforms its input objects before comparing them + * @see TransformingComparator + */ + @SuppressWarnings("unchecked") + public static Comparator transformedComparator(Comparator comparator, + final Transformer transformer) { + + if (comparator == null) { + comparator = NATURAL_COMPARATOR; + } + return new TransformingComparator(transformer, comparator); + } + + /** + * Returns the smaller of the given objects according to the given + * comparator, returning the second object if the comparator + * returns equal. + * + * @param the object type to compare + * @param o1 the first object to compare + * @param o2 the second object to compare + * @param comparator the sort order to use + * @return the smaller of the two objects + */ + @SuppressWarnings("unchecked") + public static E min(final E o1, final E o2, Comparator comparator) { + if (comparator == null) { + comparator = NATURAL_COMPARATOR; + } + final int c = comparator.compare(o1, o2); + return c < 0 ? o1 : o2; + } + + /** + * Returns the larger of the given objects according to the given + * comparator, returning the second object if the comparator + * returns equal. + * + * @param the object type to compare + * @param o1 the first object to compare + * @param o2 the second object to compare + * @param comparator the sort order to use + * @return the larger of the two objects + */ + @SuppressWarnings("unchecked") + public static E max(final E o1, final E o2, Comparator comparator) { + if (comparator == null) { + comparator = NATURAL_COMPARATOR; + } + final int c = comparator.compare(o1, o2); + return c > 0 ? o1 : o2; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/EnumerationUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/EnumerationUtils.java new file mode 100644 index 000000000..ad77b76c0 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/EnumerationUtils.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.StringTokenizer; + +import org.apache.commons.collections4.iterators.EnumerationIterator; + +/** + * Provides utility methods for {@link Enumeration} instances. + * + * @since 3.0 + * @version $Id: EnumerationUtils.java 1683009 2015-06-01 21:53:01Z tn $ + */ +public class EnumerationUtils { + + /** + * EnumerationUtils is not normally instantiated. + */ + private EnumerationUtils() {} + + /** + * Returns the index-th value in the {@link Enumeration}, throwing + * IndexOutOfBoundsException if there is no such element. + *

+ * The Enumeration is advanced to index (or to the end, if + * index exceeds the number of entries) as a side effect of this method. + * + * @param e the enumeration to get a value from + * @param index the index to get + * @param the type of object in the {@link Enumeration} + * @return the object at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + * @throws IllegalArgumentException if the object type is invalid + * @since 4.1 + */ + public static T get(final Enumeration e, final int index) { + int i = index; + CollectionUtils.checkIndexBounds(i); + while (e.hasMoreElements()) { + i--; + if (i == -1) { + return e.nextElement(); + } else { + e.nextElement(); + } + } + throw new IndexOutOfBoundsException("Entry does not exist: " + i); + } + + /** + * Creates a list based on an enumeration. + * + *

As the enumeration is traversed, an ArrayList of its values is + * created. The new list is returned.

+ * + * @param the element type + * @param enumeration the enumeration to traverse, which should not be null. + * @return a list containing all elements of the given enumeration + * @throws NullPointerException if the enumeration parameter is null. + */ + public static List toList(final Enumeration enumeration) { + return IteratorUtils.toList(new EnumerationIterator(enumeration)); + } + + /** + * Override toList(Enumeration) for StringTokenizer as it implements Enumeration<Object> + * for the sake of backward compatibility. + * + * @param stringTokenizer the tokenizer to convert to a {@link List}<{@link String}> + * @return a list containing all tokens of the given StringTokenizer + */ + public static List toList(final StringTokenizer stringTokenizer) { + final List result = new ArrayList(stringTokenizer.countTokens()); + while (stringTokenizer.hasMoreTokens()) { + result.add(stringTokenizer.nextToken()); + } + return result; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/Equator.java b/fine-commons-collections4/src/org/apache/commons/collections4/Equator.java new file mode 100644 index 000000000..4ae85fc3d --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/Equator.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable + * law or agreed to in writing, software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License + * for the specific language governing permissions and limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * An equation function, which determines equality between objects of type T. + *

+ * It is the functional sibling of {@link java.util.Comparator}; {@link Equator} is to + * {@link Object} as {@link java.util.Comparator} is to {@link java.lang.Comparable}. + * + * @param the types of object this {@link Equator} can evaluate. + * @since 4.0 + * @version $Id: Equator.java 1540567 2013-11-10 22:19:29Z tn $ + */ +public interface Equator { + /** + * Evaluates the two arguments for their equality. + * + * @param o1 the first object to be equated. + * @param o2 the second object to be equated. + * @return whether the two objects are equal. + */ + boolean equate(T o1, T o2); + + /** + * Calculates the hash for the object, based on the method of equality used in the equate + * method. This is used for classes that delegate their {@link Object#equals(Object) equals(Object)} method to an + * Equator (and so must also delegate their {@link Object#hashCode() hashCode()} method), or for implementations + * of {@link org.apache.commons.collections4.map.HashedMap} that use an Equator for the key objects. + * + * @param o the object to calculate the hash for. + * @return the hash of the object. + */ + int hash(T o); +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/Factory.java b/fine-commons-collections4/src/org/apache/commons/collections4/Factory.java new file mode 100644 index 000000000..159a50b62 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/Factory.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Defines a functor interface implemented by classes that create objects. + *

+ * A Factory creates an object without using an input parameter. + * If an input parameter is required, then {@link Transformer} is more appropriate. + *

+ * Standard implementations of common factories are provided by + * {@link FactoryUtils}. These include factories that return a constant, + * a copy of a prototype or a new instance. + * + * @param the type that the factory creates + * + * @since 2.1 + * @version $Id: Factory.java 1543256 2013-11-19 00:45:38Z ggregory $ + */ +public interface Factory { + + /** + * Create a new object. + * + * @return a new object + * @throws FunctorException (runtime) if the factory cannot create an object + */ + T create(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/FactoryUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/FactoryUtils.java new file mode 100644 index 000000000..4ddccd35d --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/FactoryUtils.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import org.apache.commons.collections4.functors.ConstantFactory; +import org.apache.commons.collections4.functors.ExceptionFactory; +import org.apache.commons.collections4.functors.InstantiateFactory; +import org.apache.commons.collections4.functors.PrototypeFactory; + +/** + * FactoryUtils provides reference implementations and utilities + * for the Factory functor interface. The supplied factories are: + *

    + *
  • Prototype - clones a specified object + *
  • Instantiate - creates objects using reflection + *
  • Constant - always returns the same object + *
  • Null - always returns null + *
  • Exception - always throws an exception + *
+ *

+ * Since v4.1 only factories which are considered to be unsafe are + * Serializable. Factories considered to be unsafe for serialization are: + *

    + *
  • Prototype + *
  • Instantiate + *
+ * + * @since 3.0 + * @version $Id: FactoryUtils.java 1714362 2015-11-14 20:38:02Z tn $ + */ +public class FactoryUtils { + + /** + * This class is not normally instantiated. + */ + private FactoryUtils() {} + + /** + * Gets a Factory that always throws an exception. + * This could be useful during testing as a placeholder. + * + * @see org.apache.commons.collections4.functors.ExceptionFactory + * + * @param the type that the factory creates + * @return the factory + */ + public static Factory exceptionFactory() { + return ExceptionFactory.exceptionFactory(); + } + + /** + * Gets a Factory that will return null each time the factory is used. + * This could be useful during testing as a placeholder. + * + * @see org.apache.commons.collections4.functors.ConstantFactory + * @param the "type" of null object the factory should return. + * @return the factory + */ + public static Factory nullFactory() { + return ConstantFactory.constantFactory(null); + } + + /** + * Creates a Factory that will return the same object each time the factory + * is used. No check is made that the object is immutable. In general, only + * immutable objects should use the constant factory. Mutable objects should + * use the prototype factory. + * + * @see org.apache.commons.collections4.functors.ConstantFactory + * + * @param the type that the factory creates + * @param constantToReturn the constant object to return each time in the factory + * @return the constant factory. + */ + public static Factory constantFactory(final T constantToReturn) { + return ConstantFactory.constantFactory(constantToReturn); + } + + /** + * Creates a Factory that will return a clone of the same prototype object + * each time the factory is used. The prototype will be cloned using one of these + * techniques (in order): + *
    + *
  • public clone method + *
  • public copy constructor + *
  • serialization clone + *
      + * + * @see org.apache.commons.collections4.functors.PrototypeFactory + * + * @param the type that the factory creates + * @param prototype the object to clone each time in the factory + * @return the prototype factory, or a {@link ConstantFactory#NULL_INSTANCE} if + * the {@code prototype} is {@code null} + * @throws IllegalArgumentException if the prototype cannot be cloned + */ + public static Factory prototypeFactory(final T prototype) { + return PrototypeFactory.prototypeFactory(prototype); + } + + /** + * Creates a Factory that can create objects of a specific type using + * a no-args constructor. + * + * @see org.apache.commons.collections4.functors.InstantiateFactory + * + * @param the type that the factory creates + * @param classToInstantiate the Class to instantiate each time in the factory + * @return the reflection factory + * @throws NullPointerException if the classToInstantiate is null + */ + public static Factory instantiateFactory(final Class classToInstantiate) { + return InstantiateFactory.instantiateFactory(classToInstantiate, null, null); + } + + /** + * Creates a Factory that can create objects of a specific type using + * the arguments specified to this method. + * + * @see org.apache.commons.collections4.functors.InstantiateFactory + * + * @param the type that the factory creates + * @param classToInstantiate the Class to instantiate each time in the factory + * @param paramTypes parameter types for the constructor, can be null + * @param args the arguments to pass to the constructor, can be null + * @return the reflection factory + * @throws NullPointerException if the classToInstantiate is null + * @throws IllegalArgumentException if the paramTypes and args don't match + * @throws IllegalArgumentException if the constructor doesn't exist + */ + public static Factory instantiateFactory(final Class classToInstantiate, final Class[] paramTypes, + final Object[] args) { + return InstantiateFactory.instantiateFactory(classToInstantiate, paramTypes, args); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/FluentIterable.java b/fine-commons-collections4/src/org/apache/commons/collections4/FluentIterable.java new file mode 100644 index 000000000..0a5420cb6 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/FluentIterable.java @@ -0,0 +1,505 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.collections4.iterators.SingletonIterator; + +/** + * A FluentIterable provides a powerful yet simple API for manipulating + * Iterable instances in a fluent manner. + *

      + * A FluentIterable can be created either from an Iterable or from a set + * of elements. The following types of methods are provided: + *

        + *
      • fluent methods which return a new {@code FluentIterable} instance, + * providing a view of the original iterable (e.g. filter(Predicate)); + *
      • conversion methods which copy the FluentIterable's contents into a + * new collection or array (e.g. toList()); + *
      • utility methods which answer questions about the FluentIterable's + * contents (e.g. size(), anyMatch(Predicate)). + *
      • + *
      + *

      + * The following example outputs the first 3 even numbers in the range [1, 10] + * into a list: + *

      + * List<String> result =
      + *   FluentIterable
      + *       .of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
      + *       .filter(new Predicate() {
      + *                   public boolean evaluate(Integer number) {
      + *                        return number % 2 == 0;
      + *                   }
      + *              )
      + *       .transform(TransformerUtils.stringValueTransformer())
      + *       .limit(3)
      + *       .toList();
      + * 
      + * The resulting list will contain the following elements: + *
      [2, 4, 6]
      + * + * @param the element type + * @since 4.1 + * @version $Id: FluentIterable.java 1684264 2015-06-08 20:06:29Z tn $ + */ +public class FluentIterable implements Iterable { + + /** A reference to the wrapped iterable. */ + private final Iterable iterable; + + // Static factory methods + // ---------------------------------------------------------------------- + + /** + * Creates a new empty FluentIterable. + * + * @param the element type + * @return a new empty FluentIterable + */ + @SuppressWarnings("unchecked") + public static FluentIterable empty() { + return IterableUtils.EMPTY_ITERABLE; + } + + /** + * Creates a new FluentIterable of the single provided element. + *

      + * The returned iterable's iterator does not support {@code remove()}. + * + * @param the element type + * @param singleton the singleton element + * @return a new FluentIterable containing the singleton + */ + public static FluentIterable of(final T singleton) { + return of(IteratorUtils.asIterable(new SingletonIterator(singleton, false))); + } + + /** + * Creates a new FluentIterable from the provided elements. + *

      + * The returned iterable's iterator does not support {@code remove()}. + * + * @param the element type + * @param elements the elements to be contained in the FluentIterable + * @return a new FluentIterable containing the provided elements + */ + public static FluentIterable of(final T... elements) { + return of(Arrays.asList(elements)); + } + + /** + * Construct a new FluentIterable from the provided iterable. If the + * iterable is already an instance of FluentIterable, the instance + * will be returned instead. + *

      + * The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + * + * @param the element type + * @param iterable the iterable to wrap into a FluentIterable, may not be null + * @return a new FluentIterable wrapping the provided iterable + * @throws NullPointerException if iterable is null + */ + public static FluentIterable of(final Iterable iterable) { + IterableUtils.checkNotNull(iterable); + if (iterable instanceof FluentIterable) { + return (FluentIterable) iterable; + } else { + return new FluentIterable(iterable); + } + } + + // Constructor + // ---------------------------------------------------------------------- + + /** + * Package-private constructor, used by IterableUtils. + */ + FluentIterable() { + this.iterable = this; + } + + /** + * Create a new FluentIterable by wrapping the provided iterable. + * @param iterable the iterable to wrap + */ + private FluentIterable(final Iterable iterable) { + this.iterable = iterable; + } + + // fluent construction methods + // ---------------------------------------------------------------------- + + /** + * Returns a new FluentIterable whose iterator will first traverse + * the elements of the current iterable, followed by the provided + * elements. + * + * @param elements the elements to append to the iterable + * @return a new iterable, combining this iterable with the elements + */ + public FluentIterable append(final E... elements) { + return append(Arrays.asList(elements)); + } + + /** + * Returns a new FluentIterable whose iterator will first traverse + * the elements of the current iterable, followed by the elements + * of the provided iterable. + * + * @param other the other iterable to combine, may not be null + * @return a new iterable, combining this iterable with other + * @throws NullPointerException if other is null + */ + public FluentIterable append(final Iterable other) { + return of(IterableUtils.chainedIterable(iterable, other)); + } + + /** + * Returns a new FluentIterable whose iterator will traverse the + * elements of the current and provided iterable in natural order. + *

      + * Example: natural ordering + *

        + *
      • this contains elements [1, 3, 5, 7] + *
      • other contains elements [2, 4, 6, 8] + *
      + *

      + * The returned iterable will traverse the elements in the following + * order: [1, 2, 3, 4, 5, 6, 7, 8] + * + * @param other the other iterable to collate, may not be null + * @return a new iterable, collating this iterable with the other in natural order + * @throws NullPointerException if other is null + * @see {@link org.apache.commons.collections4.iterators.CollatingIterator CollatingIterator} + */ + public FluentIterable collate(final Iterable other) { + return of(IterableUtils.collatedIterable(iterable, other)); + } + + /** + * Returns a new FluentIterable whose iterator will traverse the + * elements of the current and provided iterable according to the + * ordering defined by an comparator. + *

      + * Example: descending order + *

        + *
      • this contains elements [7, 5, 3, 1] + *
      • other contains elements [8, 6, 4, 2] + *
      + *

      + * The returned iterable will traverse the elements in the following + * order: [8, 7, 6, 5, 4, 3, 2, 1] + * + * @param comparator the comparator to define an ordering, may be null, + * in which case natural ordering will be used + * @param other the other iterable to collate, may not be null + * @return a new iterable, collating this iterable with the other in natural order + * @throws NullPointerException if other is null + * @see {@link org.apache.commons.collections4.iterators.CollatingIterator CollatingIterator} + */ + public FluentIterable collate(final Iterable other, + final Comparator comparator) { + return of(IterableUtils.collatedIterable(comparator, iterable, other)); + } + + /** + * This method fully traverses an iterator of this iterable and returns + * a new iterable with the same contents, but without any reference + * to the originating iterables and/or iterators. + *

      + * Calling this method is equivalent to: + *

      +     *   FluentIterable someIterable = ...;
      +     *   FluentIterable.of(someIterable.toList());
      +     * 
      + * + * @return a new iterable with the same contents as this iterable + */ + public FluentIterable eval() { + return of(toList()); + } + + /** + * Returns a new FluentIterable whose iterator will only return + * elements from this iterable matching the provided predicate. + * + * @param predicate the predicate used to filter elements + * @return a new iterable, providing a filtered view of this iterable + * @throws NullPointerException if predicate is null + */ + public FluentIterable filter(final Predicate predicate) { + return of(IterableUtils.filteredIterable(iterable, predicate)); + } + + /** + * Returns a new FluentIterable whose iterator will return at most + * the provided maximum number of elements from this iterable. + * + * @param maxSize the maximum number of elements + * @return a new iterable, providing a bounded view of this iterable + * @throws IllegalArgumentException if maxSize is negative + */ + public FluentIterable limit(final long maxSize) { + return of(IterableUtils.boundedIterable(iterable, maxSize)); + } + + /** + * Returns a new FluentIterable whose iterator will loop infinitely + * over the elements from this iterable. + * + * @return a new iterable, providing a looping view of this iterable + */ + public FluentIterable loop() { + return of(IterableUtils.loopingIterable(iterable)); + } + + /** + * Returns a new FluentIterable whose iterator will traverse the + * elements from this iterable in reverse order. + * + * @return a new iterable, providing a reversed view of this iterable + */ + public FluentIterable reverse() { + return of(IterableUtils.reversedIterable(iterable)); + } + + /** + * Returns a new FluentIterable whose iterator will skip the first + * N elements from this iterable. + * + * @param elementsToSkip the number of elements to skip + * @return a new iterable, providing a view of this iterable by skipping + * the first N elements + * @throws IllegalArgumentException if elementsToSkip is negative + */ + public FluentIterable skip(final long elementsToSkip) { + return of(IterableUtils.skippingIterable(iterable, elementsToSkip)); + } + + /** + * Returns a new FluentIterable whose iterator will return all elements + * of this iterable transformed by the provided transformer. + * + * @param the output element type + * @param transformer the transformer applied to each element + * @return a new iterable, providing a transformed view of this iterable + * @throws NullPointerException if transformer is null + */ + public FluentIterable transform(final Transformer transformer) { + return of(IterableUtils.transformedIterable(iterable, transformer)); + } + + /** + * Returns a new FluentIterable whose iterator will return a unique view + * of this iterable. + * + * @return a new iterable, providing a unique view of this iterable + */ + public FluentIterable unique() { + return of(IterableUtils.uniqueIterable(iterable)); + } + + /** + * Returns a new FluentIterable whose iterator will return an unmodifiable + * view of this iterable. + * + * @return a new iterable, providing an unmodifiable view of this iterable + */ + public FluentIterable unmodifiable() { + return of(IterableUtils.unmodifiableIterable(iterable)); + } + + /** + * Returns a new FluentIterable whose iterator will traverse + * the elements of this iterable and the other iterable in + * alternating order. + * + * @param other the other iterable to interleave, may not be null + * @return a new iterable, interleaving this iterable with others + * @throws NullPointerException if other is null + */ + public FluentIterable zip(final Iterable other) { + return of(IterableUtils.zippingIterable(iterable, other)); + } + + /** + * Returns a new FluentIterable whose iterator will traverse + * the elements of this iterable and the other iterables in + * alternating order. + * + * @param others the iterables to interleave, may not be null + * @return a new iterable, interleaving this iterable with others + * @throws NullPointerException if either of the provided iterables is null + */ + public FluentIterable zip(final Iterable... others) { + return of(IterableUtils.zippingIterable(iterable, others)); + } + + // convenience methods + // ---------------------------------------------------------------------- + + /** {@inheritDoc} */ + @Override + public Iterator iterator() { + return iterable.iterator(); + } + + /** + * Returns an Enumeration that will enumerate all elements contained + * in this iterable. + * + * @return an Enumeration over the elements of this iterable + */ + public Enumeration asEnumeration() { + return IteratorUtils.asEnumeration(iterator()); + } + + /** + * Checks if all elements contained in this iterable are matching the + * provided predicate. + *

      + * A null or empty iterable returns true. + * + * @param predicate the predicate to use, may not be null + * @return true if all elements contained in this iterable match the predicate, + * false otherwise + * @throws NullPointerException if predicate is null + */ + public boolean allMatch(final Predicate predicate) { + return IterableUtils.matchesAll(iterable, predicate); + } + + /** + * Checks if this iterable contains any element matching the provided predicate. + *

      + * A null or empty iterable returns false. + * + * @param predicate the predicate to use, may not be null + * @return true if at least one element contained in this iterable matches the predicate, + * false otherwise + * @throws NullPointerException if predicate is null + */ + public boolean anyMatch(final Predicate predicate) { + return IterableUtils.matchesAny(iterable, predicate); + } + + /** + * Checks if this iterable is empty. + * + * @return true if this iterable does not contain any elements, false otherwise + */ + public boolean isEmpty() { + return IterableUtils.isEmpty(iterable); + } + + /** + * Checks if the object is contained in this iterable. + * + * @param object the object to check + * @return true if the object is contained in this iterable, false otherwise + */ + public boolean contains(final Object object) { + return IterableUtils.contains(iterable, object); + } + + /** + * Applies the closure to all elements contained in this iterable. + * + * @param closure the closure to apply to each element, may not be null + * @throws NullPointerException if closure is null + */ + public void forEach(final Closure closure) { + IterableUtils.forEach(iterable, closure); + } + + /** + * Returns the element at the provided position in this iterable. + * In order to return the element, an iterator needs to be traversed + * up to the requested position. + * + * @param position the position of the element to return + * @return the element + * @throws IndexOutOfBoundsException if the provided position is outside the + * valid range of this iterable: [0, size) + */ + public E get(final int position) { + return IterableUtils.get(iterable, position); + } + + /** + * Returns the number of elements that are contained in this iterable. + * In order to determine the size, an iterator needs to be traversed. + * + * @return the size of this iterable + */ + public int size() { + return IterableUtils.size(iterable); + } + + /** + * Traverses an iterator of this iterable and adds all elements + * to the provided collection. + * + * @param collection the collection to add the elements + * @throws NullPointerException if collection is null + */ + public void copyInto(final Collection collection) { + if (collection == null) { + throw new NullPointerException("Collection must not be null"); + } + CollectionUtils.addAll(collection, iterable); + } + + /** + * Returns an array containing all elements of this iterable by traversing + * its iterator. + * + * @param arrayClass the class of array to create + * @return an array of the iterable contents + * @throws ArrayStoreException if arrayClass is invalid + */ + public E[] toArray(final Class arrayClass) { + return IteratorUtils.toArray(iterator(), arrayClass); + } + + /** + * Returns a mutable list containing all elements of this iterable + * by traversing its iterator. + *

      + * The returned list is guaranteed to be mutable. + * + * @return a list of the iterable contents + */ + public List toList() { + return IterableUtils.toList(iterable); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return IterableUtils.toString(iterable); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/FunctorException.java b/fine-commons-collections4/src/org/apache/commons/collections4/FunctorException.java new file mode 100644 index 000000000..5b0406ec8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/FunctorException.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Runtime exception thrown from functors. + * If required, a root cause error can be wrapped within this one. + * + * @since 3.0 + * @version $Id: FunctorException.java 1477779 2013-04-30 18:55:24Z tn $ + */ +public class FunctorException extends RuntimeException { + + /** Serialization version */ + private static final long serialVersionUID = -4704772662059351193L; + + /** + * Constructs a new FunctorException without specified + * detail message. + */ + public FunctorException() { + super(); + } + + /** + * Constructs a new FunctorException with specified + * detail message. + * + * @param msg the error message. + */ + public FunctorException(final String msg) { + super(msg); + } + + /** + * Constructs a new FunctorException with specified + * nested Throwable root cause. + * + * @param rootCause the exception or error that caused this exception + * to be thrown. + */ + public FunctorException(final Throwable rootCause) { + super(rootCause); + } + + /** + * Constructs a new FunctorException with specified + * detail message and nested Throwable root cause. + * + * @param msg the error message. + * @param rootCause the exception or error that caused this exception + * to be thrown. + */ + public FunctorException(final String msg, final Throwable rootCause) { + super(msg, rootCause); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/Get.java b/fine-commons-collections4/src/org/apache/commons/collections4/Get.java new file mode 100644 index 000000000..150c8d2ee --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/Get.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; +import java.util.Set; + +/** + * The "read" subset of the {@link java.util.Map} interface. + * + * @since 4.0 + * @version $Id: Get.java 1543265 2013-11-19 00:48:44Z ggregory $ + * + * @see Put + */ +public interface Get { + + /** + * @see java.util.Map#containsKey(Object) + */ + boolean containsKey(Object key); + + /** + * @see java.util.Map#containsValue(Object) + */ + boolean containsValue(Object value); + + /** + * @see java.util.Map#entrySet() + */ + Set> entrySet(); + + /** + * @see java.util.Map#get(Object) + */ + V get(Object key); + + /** + * @see java.util.Map#remove(Object) + */ + V remove(Object key); + + /** + * @see java.util.Map#isEmpty() + */ + boolean isEmpty(); + + /** + * @see java.util.Map#keySet() + */ + Set keySet(); + + /** + * @see java.util.Map#size() + */ + int size(); + + /** + * @see java.util.Map#values() + */ + Collection values(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/IterableGet.java b/fine-commons-collections4/src/org/apache/commons/collections4/IterableGet.java new file mode 100644 index 000000000..2035dd22f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/IterableGet.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * The "read" subset of the {@link java.util.Map} interface. + * + * @since 4.0 + * @version $Id: IterableGet.java 1477779 2013-04-30 18:55:24Z tn $ + * + * @see Put + */ +public interface IterableGet extends Get { + /** + * Obtains a MapIterator over the map. + *

      + * A map iterator is an efficient way of iterating over maps. + * There is no need to access the entry set or use Map Entry objects. + *

      +     * IterableMap map = new HashedMap();
      +     * MapIterator it = map.mapIterator();
      +     * while (it.hasNext()) {
      +     *   String key = it.next();
      +     *   Integer value = it.getValue();
      +     *   it.setValue(value + 1);
      +     * }
      +     * 
      + * + * @return a map iterator + */ + MapIterator mapIterator(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/IterableMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/IterableMap.java new file mode 100644 index 000000000..c9ab72df2 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/IterableMap.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Map; + +/** + * Defines a map that can be iterated directly without needing to create an entry set. + *

      + * A map iterator is an efficient way of iterating over maps. + * There is no need to access the entry set or use Map Entry objects. + *

      + * IterableMap map = new HashedMap();
      + * MapIterator it = map.mapIterator();
      + * while (it.hasNext()) {
      + *   String key = it.next();
      + *   Integer value = it.getValue();
      + *   it.setValue(value + 1);
      + * }
      + * 
      + * + * @param the type of the keys in the map + * @param the type of the values in the map + * + * @since 3.0 + * @version $Id: IterableMap.java 1469004 2013-04-17 17:37:03Z tn $ + */ +public interface IterableMap extends Map, Put, IterableGet { +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/IterableSortedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/IterableSortedMap.java new file mode 100644 index 000000000..a39a508d1 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/IterableSortedMap.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.SortedMap; + +/** + * {@link SortedMap} + {@link OrderedMap}. + * + * @param the type of the keys in the map + * @param the type of the values in the map + * + * @since 4.0 + * @version $Id: IterableSortedMap.java 1469004 2013-04-17 17:37:03Z tn $ + */ +public interface IterableSortedMap extends SortedMap, OrderedMap { +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/IterableUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/IterableUtils.java new file mode 100644 index 000000000..01981452e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/IterableUtils.java @@ -0,0 +1,1086 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.apache.commons.collections4.functors.EqualPredicate; +import org.apache.commons.collections4.iterators.LazyIteratorChain; +import org.apache.commons.collections4.iterators.ReverseListIterator; +import org.apache.commons.collections4.iterators.UniqueFilterIterator; + +/** + * Provides utility methods and decorators for {@link Iterable} instances. + *

      + * Note: this util class has been designed for fail-fast argument checking. + *

        + *
      • + * all decorator methods are NOT null-safe wrt the provided Iterable argument, i.e. + * they will throw a {@link NullPointerException} if a null Iterable is passed as argument. + *
      • + * all other utility methods are null-safe wrt the provided Iterable argument, i.e. they will + * treat a null Iterable the same way as an empty one. Other arguments which are null, + * e.g. a {@link Predicate}, will result in a {@link NullPointerException}. Exception: passing + * a null {@link Comparator} is equivalent to a Comparator with natural ordering. + *
      + * + * @since 4.1 + * @version $Id: IterableUtils.java 1716538 2015-11-25 20:27:49Z tn $ + */ +public class IterableUtils { + + /** + * An empty iterable. + */ + @SuppressWarnings("rawtypes") + static final FluentIterable EMPTY_ITERABLE = new FluentIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.emptyIterator(); + } + }; + + // Empty + // ---------------------------------------------------------------------- + + /** + * Gets an empty iterable. + *

      + * This iterable does not contain any elements. + * + * @param the element type + * @return an empty iterable + */ + @SuppressWarnings("unchecked") // OK, empty collection is compatible with any type + public static Iterable emptyIterable() { + return EMPTY_ITERABLE; + } + + // Chained + // ---------------------------------------------------------------------- + + /** + * Combines two iterables into a single iterable. + *

      + * The returned iterable has an iterator that traverses the elements in {@code a}, + * followed by the elements in {@code b}. The source iterators are not polled until + * necessary. + *

      + * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param a the first iterable, may not be null + * @param b the second iterable, may not be null + * @return a new iterable, combining the provided iterables + * @throws NullPointerException if either a or b is null + */ + @SuppressWarnings("unchecked") + public static Iterable chainedIterable(final Iterable a, + final Iterable b) { + return chainedIterable(new Iterable[] {a, b}); + } + + /** + * Combines three iterables into a single iterable. + *

      + * The returned iterable has an iterator that traverses the elements in {@code a}, + * followed by the elements in {@code b} and {@code c}. The source iterators are + * not polled until necessary. + *

      + * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param a the first iterable, may not be null + * @param b the second iterable, may not be null + * @param c the third iterable, may not be null + * @return a new iterable, combining the provided iterables + * @throws NullPointerException if either of the provided iterables is null + */ + @SuppressWarnings("unchecked") + public static Iterable chainedIterable(final Iterable a, + final Iterable b, + final Iterable c) { + return chainedIterable(new Iterable[] {a, b, c}); + } + + /** + * Combines four iterables into a single iterable. + *

      + * The returned iterable has an iterator that traverses the elements in {@code a}, + * followed by the elements in {@code b}, {@code c} and {@code d}. The source + * iterators are not polled until necessary. + *

      + * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param a the first iterable, may not be null + * @param b the second iterable, may not be null + * @param c the third iterable, may not be null + * @param d the fourth iterable, may not be null + * @return a new iterable, combining the provided iterables + * @throws NullPointerException if either of the provided iterables is null + */ + @SuppressWarnings("unchecked") + public static Iterable chainedIterable(final Iterable a, + final Iterable b, + final Iterable c, + final Iterable d) { + return chainedIterable(new Iterable[] {a, b, c, d}); + } + + /** + * Combines the provided iterables into a single iterable. + *

      + * The returned iterable has an iterator that traverses the elements in the order + * of the arguments, i.e. iterables[0], iterables[1], .... The source iterators + * are not polled until necessary. + *

      + * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param iterables the iterables to combine, may not be null + * @return a new iterable, combining the provided iterables + * @throws NullPointerException if either of the provided iterables is null + */ + public static Iterable chainedIterable(final Iterable... iterables) { + checkNotNull(iterables); + return new FluentIterable() { + @Override + public Iterator iterator() { + return new LazyIteratorChain() { + @Override + protected Iterator nextIterator(int count) { + if (count > iterables.length) { + return null; + } else { + return iterables[count - 1].iterator(); + } + } + }; + } + }; + } + + // Collated + // ---------------------------------------------------------------------- + + /** + * Combines the two provided iterables into an ordered iterable using + * natural ordering. + *

      + * The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + * + * @param the element type + * @param a the first iterable, may not be null + * @param b the second iterable, may not be null + * @return a filtered view on the specified iterable + * @throws NullPointerException if either of the provided iterables is null + */ + public static Iterable collatedIterable(final Iterable a, + final Iterable b) { + checkNotNull(a, b); + return new FluentIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.collatedIterator(null, a.iterator(), b.iterator()); + } + }; + } + + /** + * Combines the two provided iterables into an ordered iterable using the + * provided comparator. If the comparator is null, natural ordering will be + * used. + *

      + * The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + * + * @param the element type + * @param comparator the comparator defining an ordering over the elements, + * may be null, in which case natural ordering will be used + * @param a the first iterable, may not be null + * @param b the second iterable, may not be null + * @return a filtered view on the specified iterable + * @throws NullPointerException if either of the provided iterables is null + */ + public static Iterable collatedIterable(final Comparator comparator, + final Iterable a, + final Iterable b) { + checkNotNull(a, b); + return new FluentIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.collatedIterator(comparator, a.iterator(), b.iterator()); + } + }; + } + + // Filtered + // ---------------------------------------------------------------------- + + /** + * Returns a view of the given iterable that only contains elements matching + * the provided predicate. + *

      + * The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + * + * @param the element type + * @param iterable the iterable to filter, may not be null + * @param predicate the predicate used to filter elements, may not be null + * @return a filtered view on the specified iterable + * @throws NullPointerException if either iterable or predicate is null + */ + public static Iterable filteredIterable(final Iterable iterable, + final Predicate predicate) { + checkNotNull(iterable); + if (predicate == null) { + throw new NullPointerException("Predicate must not be null."); + } + return new FluentIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.filteredIterator(emptyIteratorIfNull(iterable), predicate); + } + }; + } + + // Bounded + // ---------------------------------------------------------------------- + + /** + * Returns a view of the given iterable that contains at most the given number + * of elements. + *

      + * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param iterable the iterable to limit, may not be null + * @param maxSize the maximum number of elements, must not be negative + * @return a bounded view on the specified iterable + * @throws IllegalArgumentException if maxSize is negative + * @throws NullPointerException if iterable is null + */ + public static Iterable boundedIterable(final Iterable iterable, final long maxSize) { + checkNotNull(iterable); + if (maxSize < 0) { + throw new IllegalArgumentException("MaxSize parameter must not be negative."); + } + + return new FluentIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.boundedIterator(iterable.iterator(), maxSize); + } + }; + } + + // Looping + // ---------------------------------------------------------------------- + + /** + * Returns a view of the given iterable which will cycle infinitely over + * its elements. + *

      + * The returned iterable's iterator supports {@code remove()} if + * {@code iterable.iterator()} does. After {@code remove()} is called, subsequent + * cycles omit the removed element, which is no longer in {@code iterable}. The + * iterator's {@code hasNext()} method returns {@code true} until {@code iterable} + * is empty. + * + * @param the element type + * @param iterable the iterable to loop, may not be null + * @return a view of the iterable, providing an infinite loop over its elements + * @throws NullPointerException if iterable is null + */ + public static Iterable loopingIterable(final Iterable iterable) { + checkNotNull(iterable); + return new FluentIterable() { + @Override + public Iterator iterator() { + return new LazyIteratorChain() { + @Override + protected Iterator nextIterator(int count) { + if (IterableUtils.isEmpty(iterable)) { + return null; + } else { + return iterable.iterator(); + } + } + }; + } + }; + } + + // Reversed + // ---------------------------------------------------------------------- + + /** + * Returns a reversed view of the given iterable. + *

      + * In case the provided iterable is a {@link List} instance, a + * {@link ReverseListIterator} will be used to reverse the traversal + * order, otherwise an intermediate {@link List} needs to be created. + *

      + * The returned iterable's iterator supports {@code remove()} if the + * provided iterable is a {@link List} instance. + * + * @param the element type + * @param iterable the iterable to use, may not be null + * @return a reversed view of the specified iterable + * @throws NullPointerException if iterable is null + * @see ReverseListIterator + */ + public static Iterable reversedIterable(final Iterable iterable) { + checkNotNull(iterable); + return new FluentIterable() { + @Override + public Iterator iterator() { + final List list = (iterable instanceof List) ? + (List) iterable : + IteratorUtils.toList(iterable.iterator()); + return new ReverseListIterator(list); + } + }; + } + + // Skipping + // ---------------------------------------------------------------------- + + /** + * Returns a view of the given iterable that skips the first N elements. + *

      + * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param iterable the iterable to use, may not be null + * @param elementsToSkip the number of elements to skip from the start, must not be negative + * @return a view of the specified iterable, skipping the first N elements + * @throws IllegalArgumentException if elementsToSkip is negative + * @throws NullPointerException if iterable is null + */ + public static Iterable skippingIterable(final Iterable iterable, final long elementsToSkip) { + checkNotNull(iterable); + if (elementsToSkip < 0) { + throw new IllegalArgumentException("ElementsToSkip parameter must not be negative."); + } + + return new FluentIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.skippingIterator(iterable.iterator(), elementsToSkip); + } + }; + } + + // Transformed + // ---------------------------------------------------------------------- + + /** + * Returns a transformed view of the given iterable where all of its elements + * have been transformed by the provided transformer. + *

      + * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the input element type + * @param the output element type + * @param iterable the iterable to transform, may not be null + * @param transformer the transformer, must not be null + * @return a transformed view of the specified iterable + * @throws NullPointerException if either iterable or transformer is null + */ + public static Iterable transformedIterable(final Iterable iterable, + final Transformer transformer) { + checkNotNull(iterable); + if (transformer == null) { + throw new NullPointerException("Transformer must not be null."); + } + return new FluentIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.transformedIterator(iterable.iterator(), transformer); + } + }; + } + + // Unique + // ---------------------------------------------------------------------- + + /** + * Returns a unique view of the given iterable. + *

      + * The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. Calling {@code remove()} + * will only remove a single element from the underlying iterator. + * + * @param the element type + * @param iterable the iterable to use, may not be null + * @return a unique view of the specified iterable + * @throws NullPointerException if iterable is null + */ + public static Iterable uniqueIterable(final Iterable iterable) { + checkNotNull(iterable); + return new FluentIterable() { + @Override + public Iterator iterator() { + return new UniqueFilterIterator(iterable.iterator()); + } + }; + } + + // Unmodifiable + // ---------------------------------------------------------------------- + + /** + * Returns an unmodifiable view of the given iterable. + *

      + * The returned iterable's iterator does not support {@code remove()}. + * + * @param the element type + * @param iterable the iterable to use, may not be null + * @return an unmodifiable view of the specified iterable + * @throws NullPointerException if iterable is null + */ + public static Iterable unmodifiableIterable(final Iterable iterable) { + checkNotNull(iterable); + if (iterable instanceof UnmodifiableIterable) { + return iterable; + } + return new UnmodifiableIterable(iterable); + } + + /** + * Inner class to distinguish unmodifiable instances. + */ + private static final class UnmodifiableIterable extends FluentIterable { + private final Iterable unmodifiable; + + public UnmodifiableIterable(final Iterable iterable) { + super(); + this.unmodifiable = iterable; + } + + @Override + public Iterator iterator() { + return IteratorUtils.unmodifiableIterator(unmodifiable.iterator()); + } + } + + // Zipping + // ---------------------------------------------------------------------- + + /** + * Interleaves two iterables into a single iterable. + *

      + * The returned iterable has an iterator that traverses the elements in {@code a} + * and {@code b} in alternating order. The source iterators are not polled until + * necessary. + *

      + * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param a the first iterable, may not be null + * @param b the second iterable, may not be null + * @return a new iterable, interleaving the provided iterables + * @throws NullPointerException if either a or b is null + */ + public static Iterable zippingIterable(final Iterable a, + final Iterable b) { + checkNotNull(a); + checkNotNull(b); + return new FluentIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.zippingIterator(a.iterator(), b.iterator()); + } + }; + } + + /** + * Interleaves two iterables into a single iterable. + *

      + * The returned iterable has an iterator that traverses the elements in {@code a} + * and {@code b} in alternating order. The source iterators are not polled until + * necessary. + *

      + * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param first the first iterable, may not be null + * @param others the array of iterables to interleave, may not be null + * @return a new iterable, interleaving the provided iterables + * @throws NullPointerException if either of the provided iterables is null + */ + public static Iterable zippingIterable(final Iterable first, + final Iterable... others) { + checkNotNull(first); + checkNotNull(others); + return new FluentIterable() { + @Override + public Iterator iterator() { + @SuppressWarnings("unchecked") // safe + Iterator[] iterators = new Iterator[others.length + 1]; + iterators[0] = first.iterator(); + for (int i = 0; i < others.length; i++) { + iterators[i + 1] = others[i].iterator(); + } + return IteratorUtils.zippingIterator(iterators); + } + }; + } + + // Utility methods + // ---------------------------------------------------------------------- + + /** + * Returns an immutable empty iterable if the argument is null, + * or the argument itself otherwise. + * + * @param the element type + * @param iterable the iterable, may be null + * @return an empty iterable if the argument is null + */ + public static Iterable emptyIfNull(final Iterable iterable) { + return iterable == null ? IterableUtils.emptyIterable() : iterable; + } + + /** + * Applies the closure to each element of the provided iterable. + * + * @param the element type + * @param iterable the iterator to use, may be null + * @param closure the closure to apply to each element, may not be null + * @throws NullPointerException if closure is null + */ + public static void forEach(final Iterable iterable, final Closure closure) { + IteratorUtils.forEach(emptyIteratorIfNull(iterable), closure); + } + + /** + * Executes the given closure on each but the last element in the iterable. + *

      + * If the input iterable is null no change is made. + * + * @param the type of object the {@link Iterable} contains + * @param iterable the iterable to get the input from, may be null + * @param closure the closure to perform, may not be null + * @return the last element in the iterable, or null if iterable is null or empty + */ + public static E forEachButLast(final Iterable iterable, final Closure closure) { + return IteratorUtils.forEachButLast(emptyIteratorIfNull(iterable), closure); + } + + /** + * Finds the first element in the given iterable which matches the given predicate. + *

      + * A null or empty iterator returns null. + * + * @param the element type + * @param iterable the iterable to search, may be null + * @param predicate the predicate to use, may not be null + * @return the first element of the iterable which matches the predicate or null if none could be found + * @throws NullPointerException if predicate is null + */ + public static E find(final Iterable iterable, final Predicate predicate) { + return IteratorUtils.find(emptyIteratorIfNull(iterable), predicate); + } + + /** + * Returns the index of the first element in the specified iterable that + * matches the given predicate. + *

      + * A null or empty iterable returns -1. + * + * @param the element type + * @param iterable the iterable to search, may be null + * @param predicate the predicate to use, may not be null + * @return the index of the first element which matches the predicate or -1 if none matches + * @throws NullPointerException if predicate is null + */ + public static int indexOf(final Iterable iterable, final Predicate predicate) { + return IteratorUtils.indexOf(emptyIteratorIfNull(iterable), predicate); + } + + /** + * Answers true if a predicate is true for every element of an iterable. + *

      + * A null or empty iterable returns true. + * + * @param the type of object the {@link Iterable} contains + * @param iterable the {@link Iterable} to use, may be null + * @param predicate the predicate to use, may not be null + * @return true if every element of the collection matches the predicate or if the + * collection is empty, false otherwise + * @throws NullPointerException if predicate is null + */ + public static boolean matchesAll(final Iterable iterable, final Predicate predicate) { + return IteratorUtils.matchesAll(emptyIteratorIfNull(iterable), predicate); + } + + /** + * Answers true if a predicate is true for any element of the iterable. + *

      + * A null or empty iterable returns false. + * + * @param the type of object the {@link Iterable} contains + * @param iterable the {@link Iterable} to use, may be null + * @param predicate the predicate to use, may not be null + * @return true if any element of the collection matches the predicate, false otherwise + * @throws NullPointerException if predicate is null + */ + public static boolean matchesAny(final Iterable iterable, final Predicate predicate) { + return IteratorUtils.matchesAny(emptyIteratorIfNull(iterable), predicate); + } + + /** + * Counts the number of elements in the input iterable that match the predicate. + *

      + * A null iterable matches no elements. + * + * @param the type of object the {@link Iterable} contains + * @param input the {@link Iterable} to get the input from, may be null + * @param predicate the predicate to use, may not be null + * @return the number of matches for the predicate in the collection + * @throws NullPointerException if predicate is null + */ + public static long countMatches(final Iterable input, final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null."); + } + return size(filteredIterable(emptyIfNull(input), predicate)); + } + + /** + * Answers true if the provided iterable is empty. + *

      + * A null iterable returns true. + * + * @param iterable the {@link Iterable to use}, may be null + * @return true if the iterable is null or empty, false otherwise + */ + public static boolean isEmpty(final Iterable iterable) { + if (iterable instanceof Collection) { + return ((Collection) iterable).isEmpty(); + } else { + return IteratorUtils.isEmpty(emptyIteratorIfNull(iterable)); + } + } + + /** + * Checks if the object is contained in the given iterable. + *

      + * A null or empty iterable returns false. + * + * @param the type of object the {@link Iterable} contains + * @param iterable the iterable to check, may be null + * @param object the object to check + * @return true if the object is contained in the iterable, false otherwise + */ + public static boolean contains(final Iterable iterable, final Object object) { + if (iterable instanceof Collection) { + return ((Collection) iterable).contains(object); + } else { + return IteratorUtils.contains(emptyIteratorIfNull(iterable), object); + } + } + + /** + * Checks if the object is contained in the given iterable. Object equality + * is tested with an {@code equator} unlike {@link #contains(Iterable, Object)} + * which uses {@link Object#equals(Object)}. + *

      + * A null or empty iterable returns false. + * A null object will not be passed to the equator, instead a + * {@link org.apache.commons.collections4.functors.NullPredicate NullPredicate} + * will be used. + * + * @param the type of object the {@link Iterable} contains + * @param iterable the iterable to check, may be null + * @param object the object to check + * @param equator the equator to use to check, may not be null + * @return true if the object is contained in the iterable, false otherwise + * @throws NullPointerException if equator is null + */ + public static boolean contains(final Iterable iterable, final E object, + final Equator equator) { + if (equator == null) { + throw new NullPointerException("Equator must not be null."); + } + return matchesAny(iterable, EqualPredicate.equalPredicate(object, equator)); + } + + /** + * Returns the number of occurrences of the provided object in the iterable. + * + * @param the element type that the {@link Iterable} may contain + * @param the element type of the object to find + * @param iterable the {@link Iterable} to search + * @param obj the object to find the cardinality of + * @return the the number of occurrences of obj in iterable + */ + public static int frequency(final Iterable iterable, final T obj) { + if (iterable instanceof Set) { + return ((Set) iterable).contains(obj) ? 1 : 0; + } + if (iterable instanceof Bag) { + return ((Bag) iterable).getCount(obj); + } + return size(filteredIterable(emptyIfNull(iterable), EqualPredicate.equalPredicate(obj))); + } + + /** + * Returns the index-th value in the iterable's {@link Iterator}, throwing + * IndexOutOfBoundsException if there is no such element. + *

      + * If the {@link Iterable} is a {@link List}, then it will use {@link List#get(int)}. + * + * @param the type of object in the {@link Iterable}. + * @param iterable the {@link Iterable} to get a value from, may be null + * @param index the index to get + * @return the object at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + */ + public static T get(final Iterable iterable, final int index) { + CollectionUtils.checkIndexBounds(index); + if (iterable instanceof List) { + return ((List) iterable).get(index); + } + return IteratorUtils.get(emptyIteratorIfNull(iterable), index); + } + + /** + * Returns the number of elements contained in the given iterator. + *

      + * A null or empty iterator returns {@code 0}. + * + * @param iterable the iterable to check, may be null + * @return the number of elements contained in the iterable + */ + public static int size(final Iterable iterable) { + if (iterable instanceof Collection) { + return ((Collection) iterable).size(); + } else { + return IteratorUtils.size(emptyIteratorIfNull(iterable)); + } + } + + /** + * Partitions all elements from iterable into separate output collections, + * based on the evaluation of the given predicate. + *

      + * For each predicate, the result will contain a list holding all elements of the + * input iterable matching the predicate. The last list will hold all elements + * which didn't match any predicate: + *

      +     *  [C1, R] = partition(I, P1) with
      +     *  I = input
      +     *  P1 = first predicate
      +     *  C1 = collection of elements matching P1
      +     *  R = collection of elements rejected by all predicates
      +     * 
      + *

      + * If the input iterable is null, the same is returned as for an + * empty iterable. + *

      + * Example: for an input list [1, 2, 3, 4, 5] calling partition with a predicate [x < 3] + * will result in the following output: [[1, 2], [3, 4, 5]]. + * + * @param the type of object the {@link Iterable} contains + * @param iterable the iterable to partition, may be null + * @param predicate the predicate to use, may not be null + * @return a list containing the output collections + * @throws NullPointerException if predicate is null + */ + public static List> partition(final Iterable iterable, + final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null."); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) // safe + final Factory> factory = FactoryUtils.instantiateFactory((Class) ArrayList.class); + @SuppressWarnings("unchecked") // safe + final Predicate[] predicates = new Predicate[] { predicate }; + return partition(iterable, factory, predicates); + } + + /** + * Partitions all elements from iterable into separate output collections, + * based on the evaluation of the given predicates. + *

      + * For each predicate, the result will contain a list holding all elements of the + * input iterable matching the predicate. The last list will hold all elements + * which didn't match any predicate: + *

      +     *  [C1, C2, R] = partition(I, P1, P2) with
      +     *  I = input
      +     *  P1 = first predicate
      +     *  P2 = second predicate
      +     *  C1 = collection of elements matching P1
      +     *  C2 = collection of elements matching P2
      +     *  R = collection of elements rejected by all predicates
      +     * 
      + *

      + * Note: elements are only added to the output collection of the first matching + * predicate, determined by the order of arguments. + *

      + * If the input iterable is null, the same is returned as for an + * empty iterable. + *

      + * Example: for an input list [1, 2, 3, 4, 5] calling partition with predicates [x < 3] + * and [x < 5] will result in the following output: [[1, 2], [3, 4], [5]]. + * + * @param the type of object the {@link Iterable} contains + * @param iterable the collection to get the input from, may be null + * @param predicates the predicates to use, may not be null + * @return a list containing the output collections + * @throws NullPointerException if any predicate is null + */ + public static List> partition(final Iterable iterable, + final Predicate... predicates) { + + @SuppressWarnings({ "unchecked", "rawtypes" }) // safe + final Factory> factory = FactoryUtils.instantiateFactory((Class) ArrayList.class); + return partition(iterable, factory, predicates); + } + + /** + * Partitions all elements from iterable into separate output collections, + * based on the evaluation of the given predicates. + *

      + * For each predicate, the returned list will contain a collection holding + * all elements of the input iterable matching the predicate. The last collection + * contained in the list will hold all elements which didn't match any predicate: + *

      +     *  [C1, C2, R] = partition(I, P1, P2) with
      +     *  I = input
      +     *  P1 = first predicate
      +     *  P2 = second predicate
      +     *  C1 = collection of elements matching P1
      +     *  C2 = collection of elements matching P2
      +     *  R = collection of elements rejected by all predicates
      +     * 
      + *

      + * Note: elements are only added to the output collection of the first matching + * predicate, determined by the order of arguments. + *

      + * If the input iterable is null, the same is returned as for an + * empty iterable. + * If no predicates have been provided, all elements of the input collection + * will be added to the rejected collection. + *

      + * Example: for an input list [1, 2, 3, 4, 5] calling partition with predicates [x < 3] + * and [x < 5] will result in the following output: [[1, 2], [3, 4], [5]]. + * + * @param the type of object the {@link Iterable} contains + * @param the type of the output {@link Collection} + * @param iterable the collection to get the input from, may be null + * @param partitionFactory the factory used to create the output collections + * @param predicates the predicates to use, may not be null + * @return a list containing the output collections + * @throws NullPointerException if any predicate is null + */ + public static > List partition(final Iterable iterable, + final Factory partitionFactory, final Predicate... predicates) { + + if (iterable == null) { + final Iterable empty = emptyIterable(); + return partition(empty, partitionFactory, predicates); + } + + if (predicates == null) { + throw new NullPointerException("Predicates must not be null."); + } + + for (Predicate p : predicates) { + if (p == null) { + throw new NullPointerException("Predicate must not be null."); + } + } + + if (predicates.length < 1) { + // return the entire input collection as a single partition + final R singlePartition = partitionFactory.create(); + CollectionUtils.addAll(singlePartition, iterable); + return Collections.singletonList(singlePartition); + } + + // create the empty partitions + final int numberOfPredicates = predicates.length; + final int numberOfPartitions = numberOfPredicates + 1; + final List partitions = new ArrayList(numberOfPartitions); + for (int i = 0; i < numberOfPartitions; ++i) { + partitions.add(partitionFactory.create()); + } + + // for each element in inputCollection: + // find the first predicate that evaluates to true. + // if there is a predicate, add the element to the corresponding partition. + // if there is no predicate, add it to the last, catch-all partition. + for (final O element : iterable) { + boolean elementAssigned = false; + for (int i = 0; i < numberOfPredicates; ++i) { + if (predicates[i].evaluate(element)) { + partitions.get(i).add(element); + elementAssigned = true; + break; + } + } + + if (!elementAssigned) { + // no predicates evaluated to true + // add element to last partition + partitions.get(numberOfPredicates).add(element); + } + } + + return partitions; + } + + /** + * Gets a new list with the contents of the provided iterable. + * + * @param the element type + * @param iterable the iterable to use, may be null + * @return a list of the iterator contents + */ + public static List toList(final Iterable iterable) { + return IteratorUtils.toList(emptyIteratorIfNull(iterable)); + } + + /** + * Returns a string representation of the elements of the specified iterable. + *

      + * The string representation consists of a list of the iterable's elements, + * enclosed in square brackets ({@code "[]"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). Elements are + * converted to strings as by {@code String.valueOf(Object)}. + * + * @param the element type + * @param iterable the iterable to convert to a string, may be null + * @return a string representation of {@code iterable} + */ + public static String toString(final Iterable iterable) { + return IteratorUtils.toString(emptyIteratorIfNull(iterable)); + } + + /** + * Returns a string representation of the elements of the specified iterable. + *

      + * The string representation consists of a list of the iterable's elements, + * enclosed in square brackets ({@code "[]"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). Elements are + * converted to strings as by using the provided {@code transformer}. + * + * @param the element type + * @param iterable the iterable to convert to a string, may be null + * @param transformer the transformer used to get a string representation of an element + * @return a string representation of {@code iterable} + * @throws NullPointerException if {@code transformer} is null + */ + public static String toString(final Iterable iterable, + final Transformer transformer) { + if (transformer == null) { + throw new NullPointerException("Transformer must not be null."); + } + return IteratorUtils.toString(emptyIteratorIfNull(iterable), transformer); + } + + /** + * Returns a string representation of the elements of the specified iterable. + *

      + * The string representation consists of a list of the iterable's elements, + * enclosed by the provided {@code prefix} and {@code suffix}. Adjacent elements + * are separated by the provided {@code delimiter}. Elements are converted to + * strings as by using the provided {@code transformer}. + * + * @param the element type + * @param iterable the iterable to convert to a string, may be null + * @param transformer the transformer used to get a string representation of an element + * @param delimiter the string to delimit elements + * @param prefix the prefix, prepended to the string representation + * @param suffix the suffix, appended to the string representation + * @return a string representation of {@code iterable} + * @throws NullPointerException if either transformer, delimiter, prefix or suffix is null + */ + public static String toString(final Iterable iterable, + final Transformer transformer, + final String delimiter, + final String prefix, + final String suffix) { + return IteratorUtils.toString(emptyIteratorIfNull(iterable), + transformer, delimiter, prefix, suffix); + } + + // Helper methods + // ---------------------------------------------------------------------- + + /** + * Fail-fast check for null arguments. + * + * @param iterable the iterable to check + * @throws NullPointerException if iterable is null + */ + static void checkNotNull(final Iterable iterable) { + if (iterable == null) { + throw new NullPointerException("Iterable must not be null."); + } + } + + /** + * Fail-fast check for null arguments. + * + * @param iterable the iterable to check + * @throws NullPointerException if the argument or any of its contents is null + */ + static void checkNotNull(final Iterable... iterables) { + if (iterables == null) { + throw new NullPointerException("Iterables must not be null."); + } + for (final Iterable iterable : iterables) { + checkNotNull(iterable); + } + } + + /** + * Returns an empty iterator if the argument is null, + * or {@code iterable.iterator()} otherwise. + * + * @param the element type + * @param iterable the iterable, possibly null + * @return an empty iterator if the argument is null + */ + private static Iterator emptyIteratorIfNull(final Iterable iterable) { + return iterable != null ? iterable.iterator() : IteratorUtils.emptyIterator(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/IteratorUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/IteratorUtils.java new file mode 100644 index 000000000..a2e77a2fb --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/IteratorUtils.java @@ -0,0 +1,1526 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.apache.commons.collections4.functors.EqualPredicate; +import org.apache.commons.collections4.iterators.ArrayIterator; +import org.apache.commons.collections4.iterators.ArrayListIterator; +import org.apache.commons.collections4.iterators.BoundedIterator; +import org.apache.commons.collections4.iterators.CollatingIterator; +import org.apache.commons.collections4.iterators.EmptyIterator; +import org.apache.commons.collections4.iterators.EmptyListIterator; +import org.apache.commons.collections4.iterators.EmptyMapIterator; +import org.apache.commons.collections4.iterators.EmptyOrderedIterator; +import org.apache.commons.collections4.iterators.EmptyOrderedMapIterator; +import org.apache.commons.collections4.iterators.EnumerationIterator; +import org.apache.commons.collections4.iterators.FilterIterator; +import org.apache.commons.collections4.iterators.FilterListIterator; +import org.apache.commons.collections4.iterators.IteratorChain; +import org.apache.commons.collections4.iterators.IteratorEnumeration; +import org.apache.commons.collections4.iterators.IteratorIterable; +import org.apache.commons.collections4.iterators.ListIteratorWrapper; +import org.apache.commons.collections4.iterators.LoopingIterator; +import org.apache.commons.collections4.iterators.LoopingListIterator; +import org.apache.commons.collections4.iterators.NodeListIterator; +import org.apache.commons.collections4.iterators.ObjectArrayIterator; +import org.apache.commons.collections4.iterators.ObjectArrayListIterator; +import org.apache.commons.collections4.iterators.ObjectGraphIterator; +import org.apache.commons.collections4.iterators.PeekingIterator; +import org.apache.commons.collections4.iterators.PushbackIterator; +import org.apache.commons.collections4.iterators.SingletonIterator; +import org.apache.commons.collections4.iterators.SingletonListIterator; +import org.apache.commons.collections4.iterators.SkippingIterator; +import org.apache.commons.collections4.iterators.TransformIterator; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; +import org.apache.commons.collections4.iterators.UnmodifiableListIterator; +import org.apache.commons.collections4.iterators.UnmodifiableMapIterator; +import org.apache.commons.collections4.iterators.ZippingIterator; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Provides static utility methods and decorators for {@link Iterator} + * instances. The implementations are provided in the iterators subpackage. + * + * @since 2.1 + * @version $Id: IteratorUtils.java 1715973 2015-11-23 21:42:01Z tn $ + */ +public class IteratorUtils { + // validation is done in this class in certain cases because the + // public classes allow invalid states + + /** + * An iterator over no elements. + */ + @SuppressWarnings("rawtypes") + public static final ResettableIterator EMPTY_ITERATOR = EmptyIterator.RESETTABLE_INSTANCE; + + /** + * A list iterator over no elements. + */ + @SuppressWarnings("rawtypes") + public static final ResettableListIterator EMPTY_LIST_ITERATOR = EmptyListIterator.RESETTABLE_INSTANCE; + + /** + * An ordered iterator over no elements. + */ + @SuppressWarnings("rawtypes") + public static final OrderedIterator EMPTY_ORDERED_ITERATOR = EmptyOrderedIterator.INSTANCE; + + /** + * A map iterator over no elements. + */ + @SuppressWarnings("rawtypes") + public static final MapIterator EMPTY_MAP_ITERATOR = EmptyMapIterator.INSTANCE; + + /** + * An ordered map iterator over no elements. + */ + @SuppressWarnings("rawtypes") + public static final OrderedMapIterator EMPTY_ORDERED_MAP_ITERATOR = EmptyOrderedMapIterator.INSTANCE; + + /** + * Default prefix used while converting an Iterator to its String representation. + */ + private static final String DEFAULT_TOSTRING_PREFIX = "["; + + /** + * Default suffix used while converting an Iterator to its String representation. + */ + private static final String DEFAULT_TOSTRING_SUFFIX = "]"; + + /** + * Default delimiter used to delimit elements while converting an Iterator + * to its String representation. + */ + private static final String DEFAULT_TOSTRING_DELIMITER = ", "; + + /** + * IteratorUtils is not normally instantiated. + */ + private IteratorUtils() {} + + // Empty + //----------------------------------------------------------------------- + /** + * Gets an empty iterator. + *

      + * This iterator is a valid iterator object that will iterate over nothing. + * + * @param the element type + * @return an iterator over nothing + */ + public static ResettableIterator emptyIterator() { + return EmptyIterator.resettableEmptyIterator(); + } + + /** + * Gets an empty list iterator. + *

      + * This iterator is a valid list iterator object that will iterate + * over nothing. + * + * @param the element type + * @return a list iterator over nothing + */ + public static ResettableListIterator emptyListIterator() { + return EmptyListIterator.resettableEmptyListIterator(); + } + + /** + * Gets an empty ordered iterator. + *

      + * This iterator is a valid iterator object that will iterate + * over nothing. + * + * @param the element type + * @return an ordered iterator over nothing + */ + public static OrderedIterator emptyOrderedIterator() { + return EmptyOrderedIterator.emptyOrderedIterator(); + } + + /** + * Gets an empty map iterator. + *

      + * This iterator is a valid map iterator object that will iterate + * over nothing. + * + * @param the key type + * @param the value type + * @return a map iterator over nothing + */ + public static MapIterator emptyMapIterator() { + return EmptyMapIterator.emptyMapIterator(); + } + + /** + * Gets an empty ordered map iterator. + *

      + * This iterator is a valid map iterator object that will iterate + * over nothing. + * + * @param the key type + * @param the value type + * @return a map iterator over nothing + */ + public static OrderedMapIterator emptyOrderedMapIterator() { + return EmptyOrderedMapIterator.emptyOrderedMapIterator(); + } + + // Singleton + //----------------------------------------------------------------------- + /** + * Gets a singleton iterator. + *

      + * This iterator is a valid iterator object that will iterate over + * the specified object. + * + * @param the element type + * @param object the single object over which to iterate + * @return a singleton iterator over the object + */ + public static ResettableIterator singletonIterator(final E object) { + return new SingletonIterator(object); + } + + /** + * Gets a singleton list iterator. + *

      + * This iterator is a valid list iterator object that will iterate over + * the specified object. + * + * @param the element type + * @param object the single object over which to iterate + * @return a singleton list iterator over the object + */ + public static ListIterator singletonListIterator(final E object) { + return new SingletonListIterator(object); + } + + // Arrays + //----------------------------------------------------------------------- + /** + * Gets an iterator over an object array. + * + * @param the element type + * @param array the array over which to iterate + * @return an iterator over the array + * @throws NullPointerException if array is null + */ + public static ResettableIterator arrayIterator(final E... array) { + return new ObjectArrayIterator(array); + } + + /** + * Gets an iterator over an object or primitive array. + *

      + * This method will handle primitive arrays as well as object arrays. + * The primitives will be wrapped in the appropriate wrapper class. + * + * @param the element type + * @param array the array over which to iterate + * @return an iterator over the array + * @throws IllegalArgumentException if the array is not an array + * @throws NullPointerException if array is null + */ + public static ResettableIterator arrayIterator(final Object array) { + return new ArrayIterator(array); + } + + /** + * Gets an iterator over the end part of an object array. + * + * @param the element type + * @param array the array over which to iterate + * @param start the index to start iterating at + * @return an iterator over part of the array + * @throws IndexOutOfBoundsException if start is less than zero or greater + * than the length of the array + * @throws NullPointerException if array is null + */ + public static ResettableIterator arrayIterator(final E[] array, final int start) { + return new ObjectArrayIterator(array, start); + } + + /** + * Gets an iterator over the end part of an object or primitive array. + *

      + * This method will handle primitive arrays as well as object arrays. + * The primitives will be wrapped in the appropriate wrapper class. + * + * @param the element type + * @param array the array over which to iterate + * @param start the index to start iterating at + * @return an iterator over part of the array + * @throws IllegalArgumentException if the array is not an array + * @throws IndexOutOfBoundsException if start is less than zero or greater + * than the length of the array + * @throws NullPointerException if array is null + */ + public static ResettableIterator arrayIterator(final Object array, final int start) { + return new ArrayIterator(array, start); + } + + /** + * Gets an iterator over part of an object array. + * + * @param the element type + * @param array the array over which to iterate + * @param start the index to start iterating at + * @param end the index to finish iterating at + * @return an iterator over part of the array + * @throws IndexOutOfBoundsException if array bounds are invalid + * @throws IllegalArgumentException if end is before start + * @throws NullPointerException if array is null + */ + public static ResettableIterator arrayIterator(final E[] array, final int start, final int end) { + return new ObjectArrayIterator(array, start, end); + } + + /** + * Gets an iterator over part of an object or primitive array. + *

      + * This method will handle primitive arrays as well as object arrays. + * The primitives will be wrapped in the appropriate wrapper class. + * + * @param the element type + * @param array the array over which to iterate + * @param start the index to start iterating at + * @param end the index to finish iterating at + * @return an iterator over part of the array + * @throws IllegalArgumentException if the array is not an array or end is before start + * @throws IndexOutOfBoundsException if array bounds are invalid + * @throws NullPointerException if array is null + */ + public static ResettableIterator arrayIterator(final Object array, final int start, final int end) { + return new ArrayIterator(array, start, end); + } + + //----------------------------------------------------------------------- + /** + * Gets a list iterator over an object array. + * + * @param the element type + * @param array the array over which to iterate + * @return a list iterator over the array + * @throws NullPointerException if array is null + */ + public static ResettableListIterator arrayListIterator(final E... array) { + return new ObjectArrayListIterator(array); + } + + /** + * Gets a list iterator over an object or primitive array. + *

      + * This method will handle primitive arrays as well as object arrays. + * The primitives will be wrapped in the appropriate wrapper class. + * + * @param the element type + * @param array the array over which to iterate + * @return a list iterator over the array + * @throws IllegalArgumentException if the array is not an array + * @throws NullPointerException if array is null + */ + public static ResettableListIterator arrayListIterator(final Object array) { + return new ArrayListIterator(array); + } + + /** + * Gets a list iterator over the end part of an object array. + * + * @param the element type + * @param array the array over which to iterate + * @param start the index to start iterating at + * @return a list iterator over part of the array + * @throws IndexOutOfBoundsException if start is less than zero + * @throws NullPointerException if array is null + */ + public static ResettableListIterator arrayListIterator(final E[] array, final int start) { + return new ObjectArrayListIterator(array, start); + } + + /** + * Gets a list iterator over the end part of an object or primitive array. + *

      + * This method will handle primitive arrays as well as object arrays. + * The primitives will be wrapped in the appropriate wrapper class. + * + * @param the element type + * @param array the array over which to iterate + * @param start the index to start iterating at + * @return a list iterator over part of the array + * @throws IllegalArgumentException if the array is not an array + * @throws IndexOutOfBoundsException if start is less than zero + * @throws NullPointerException if array is null + */ + public static ResettableListIterator arrayListIterator(final Object array, final int start) { + return new ArrayListIterator(array, start); + } + + /** + * Gets a list iterator over part of an object array. + * + * @param the element type + * @param array the array over which to iterate + * @param start the index to start iterating at + * @param end the index to finish iterating at + * @return a list iterator over part of the array + * @throws IndexOutOfBoundsException if array bounds are invalid + * @throws IllegalArgumentException if end is before start + * @throws NullPointerException if array is null + */ + public static ResettableListIterator arrayListIterator(final E[] array, final int start, final int end) { + return new ObjectArrayListIterator(array, start, end); + } + + /** + * Gets a list iterator over part of an object or primitive array. + *

      + * This method will handle primitive arrays as well as object arrays. + * The primitives will be wrapped in the appropriate wrapper class. + * + * @param the element type + * @param array the array over which to iterate + * @param start the index to start iterating at + * @param end the index to finish iterating at + * @return a list iterator over part of the array + * @throws IllegalArgumentException if the array is not an array or end is before start + * @throws IndexOutOfBoundsException if array bounds are invalid + * @throws NullPointerException if array is null + */ + public static ResettableListIterator arrayListIterator(final Object array, final int start, final int end) { + return new ArrayListIterator(array, start, end); + } + + // Bounded + //----------------------------------------------------------------------- + /** + * Decorates the specified iterator to return at most the given number + * of elements. + * + * @param the element type + * @param iterator the iterator to decorate + * @param max the maximum number of elements returned by this iterator + * @return a new bounded iterator + * @throws NullPointerException if the iterator is null + * @throws IllegalArgumentException if max is negative + * @since 4.1 + */ + public static BoundedIterator boundedIterator(final Iterator iterator, long max) { + return boundedIterator(iterator, 0, max); + } + + /** + * Decorates the specified iterator to return at most the given number + * of elements, skipping all elements until the iterator reaches the + * position at {@code offset}. + *

      + * The iterator is immediately advanced until it reaches the position at + * {@code offset}, incurring O(n) time. + * + * @param the element type + * @param iterator the iterator to decorate + * @param offset the index of the first element of the decorated iterator to return + * @param max the maximum number of elements returned by this iterator + * @return a new bounded iterator + * @throws NullPointerException if the iterator is null + * @throws IllegalArgumentException if either offset or max is negative + * @since 4.1 + */ + public static BoundedIterator boundedIterator(final Iterator iterator, + long offset, long max) { + return new BoundedIterator(iterator, offset, max); + } + + // Unmodifiable + //----------------------------------------------------------------------- + /** + * Gets an immutable version of an {@link Iterator}. The returned object + * will always throw an {@link UnsupportedOperationException} for + * the {@link Iterator#remove} method. + * + * @param the element type + * @param iterator the iterator to make immutable + * @return an immutable version of the iterator + */ + public static Iterator unmodifiableIterator(final Iterator iterator) { + return UnmodifiableIterator.unmodifiableIterator(iterator); + } + + /** + * Gets an immutable version of a {@link ListIterator}. The returned object + * will always throw an {@link UnsupportedOperationException} for + * the {@link Iterator#remove}, {@link ListIterator#add} and + * {@link ListIterator#set} methods. + * + * @param the element type + * @param listIterator the iterator to make immutable + * @return an immutable version of the iterator + */ + public static ListIterator unmodifiableListIterator(final ListIterator listIterator) { + return UnmodifiableListIterator.umodifiableListIterator(listIterator); + } + + /** + * Gets an immutable version of a {@link MapIterator}. The returned object + * will always throw an {@link UnsupportedOperationException} for + * the {@link Iterator#remove}, {@link MapIterator#setValue(Object)} methods. + * + * @param the key type + * @param the value type + * @param mapIterator the iterator to make immutable + * @return an immutable version of the iterator + */ + public static MapIterator unmodifiableMapIterator(final MapIterator mapIterator) { + return UnmodifiableMapIterator.unmodifiableMapIterator(mapIterator); + } + + // Chained + //----------------------------------------------------------------------- + + /** + * Gets an iterator that iterates through two {@link Iterator}s + * one after another. + * + * @param the element type + * @param iterator1 the first iterator to use, not null + * @param iterator2 the second iterator to use, not null + * @return a combination iterator over the iterators + * @throws NullPointerException if either iterator is null + */ + public static Iterator chainedIterator(final Iterator iterator1, + final Iterator iterator2) { + // keep a version with two iterators to avoid the following warning in client code (Java 5 & 6) + // "A generic array of E is created for a varargs parameter" + return new IteratorChain(iterator1, iterator2); + } + + /** + * Gets an iterator that iterates through an array of {@link Iterator}s + * one after another. + * + * @param the element type + * @param iterators the iterators to use, not null or empty or contain nulls + * @return a combination iterator over the iterators + * @throws NullPointerException if iterators array is null or contains a null + */ + public static Iterator chainedIterator(final Iterator... iterators) { + return new IteratorChain(iterators); + } + + /** + * Gets an iterator that iterates through a collections of {@link Iterator}s + * one after another. + * + * @param the element type + * @param iterators the iterators to use, not null or empty or contain nulls + * @return a combination iterator over the iterators + * @throws NullPointerException if iterators collection is null or contains a null + * @throws ClassCastException if the iterators collection contains the wrong object type + */ + public static Iterator chainedIterator(final Collection> iterators) { + return new IteratorChain(iterators); + } + + // Collated + //----------------------------------------------------------------------- + /** + * Gets an iterator that provides an ordered iteration over the elements + * contained in a collection of ordered {@link Iterator}s. + *

      + * Given two ordered {@link Iterator}s A and B, + * the {@link Iterator#next()} method will return the lesser of + * A.next() and B.next(). + *

      + * The comparator is optional. If null is specified then natural order is used. + * + * @param the element type + * @param comparator the comparator to use, may be null for natural order + * @param iterator1 the first iterators to use, not null + * @param iterator2 the first iterators to use, not null + * @return a combination iterator over the iterators + * @throws NullPointerException if either iterator is null + */ + public static Iterator collatedIterator(final Comparator comparator, + final Iterator iterator1, + final Iterator iterator2) { + @SuppressWarnings("unchecked") + final Comparator comp = + comparator == null ? ComparatorUtils.NATURAL_COMPARATOR : (Comparator) comparator; + return new CollatingIterator(comp, iterator1, iterator2); + } + + /** + * Gets an iterator that provides an ordered iteration over the elements + * contained in an array of {@link Iterator}s. + *

      + * Given two ordered {@link Iterator}s A and B, + * the {@link Iterator#next()} method will return the lesser of + * A.next() and B.next() and so on. + *

      + * The comparator is optional. If null is specified then natural order is used. + * + * @param the element type + * @param comparator the comparator to use, may be null for natural order + * @param iterators the iterators to use, not null or empty or contain nulls + * @return a combination iterator over the iterators + * @throws NullPointerException if iterators array is null or contains a null value + */ + public static Iterator collatedIterator(final Comparator comparator, + final Iterator... iterators) { + @SuppressWarnings("unchecked") + final Comparator comp = + comparator == null ? ComparatorUtils.NATURAL_COMPARATOR : (Comparator) comparator; + return new CollatingIterator(comp, iterators); + } + + /** + * Gets an iterator that provides an ordered iteration over the elements + * contained in a collection of {@link Iterator}s. + *

      + * Given two ordered {@link Iterator}s A and B, + * the {@link Iterator#next()} method will return the lesser of + * A.next() and B.next() and so on. + *

      + * The comparator is optional. If null is specified then natural order is used. + * + * @param the element type + * @param comparator the comparator to use, may be null for natural order + * @param iterators the iterators to use, not null or empty or contain nulls + * @return a combination iterator over the iterators + * @throws NullPointerException if iterators collection is null or contains a null + * @throws ClassCastException if the iterators collection contains the wrong object type + */ + public static Iterator collatedIterator(final Comparator comparator, + final Collection> iterators) { + @SuppressWarnings("unchecked") + final Comparator comp = + comparator == null ? ComparatorUtils.NATURAL_COMPARATOR : (Comparator) comparator; + return new CollatingIterator(comp, iterators); + } + + // Object Graph + //----------------------------------------------------------------------- + /** + * Gets an iterator that operates over an object graph. + *

      + * This iterator can extract multiple objects from a complex tree-like object graph. + * The iteration starts from a single root object. + * It uses a Transformer to extract the iterators and elements. + * Its main benefit is that no intermediate List is created. + *

      + * For example, consider an object graph: + *

      +     *                 |- Branch -- Leaf
      +     *                 |         \- Leaf
      +     *         |- Tree |         /- Leaf
      +     *         |       |- Branch -- Leaf
      +     *  Forest |                 \- Leaf
      +     *         |       |- Branch -- Leaf
      +     *         |       |         \- Leaf
      +     *         |- Tree |         /- Leaf
      +     *                 |- Branch -- Leaf
      +     *                 |- Branch -- Leaf
      + * The following Transformer, used in this class, will extract all + * the Leaf objects without creating a combined intermediate list: + *
      +     * public Object transform(Object input) {
      +     *   if (input instanceof Forest) {
      +     *     return ((Forest) input).treeIterator();
      +     *   }
      +     *   if (input instanceof Tree) {
      +     *     return ((Tree) input).branchIterator();
      +     *   }
      +     *   if (input instanceof Branch) {
      +     *     return ((Branch) input).leafIterator();
      +     *   }
      +     *   if (input instanceof Leaf) {
      +     *     return input;
      +     *   }
      +     *   throw new ClassCastException();
      +     * }
      + *

      + * Internally, iteration starts from the root object. When next is called, + * the transformer is called to examine the object. The transformer will return + * either an iterator or an object. If the object is an Iterator, the next element + * from that iterator is obtained and the process repeats. If the element is an object + * it is returned. + *

      + * Under many circumstances, linking Iterators together in this manner is + * more efficient (and convenient) than using nested for loops to extract a list. + * + * @param the element type + * @param root the root object to start iterating from, null results in an empty iterator + * @param transformer the transformer to use, see above, null uses no effect transformer + * @return a new object graph iterator + * @since 3.1 + */ + public static Iterator objectGraphIterator(final E root, + final Transformer transformer) { + return new ObjectGraphIterator(root, transformer); + } + + // Transformed + //----------------------------------------------------------------------- + /** + * Gets an iterator that transforms the elements of another iterator. + *

      + * The transformation occurs during the next() method and the underlying + * iterator is unaffected by the transformation. + * + * @param the input type + * @param the output type + * @param iterator the iterator to use, not null + * @param transform the transform to use, not null + * @return a new transforming iterator + * @throws NullPointerException if either parameter is null + */ + public static Iterator transformedIterator(final Iterator iterator, + final Transformer transform) { + + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + if (transform == null) { + throw new NullPointerException("Transformer must not be null"); + } + return new TransformIterator(iterator, transform); + } + + // Filtered + //----------------------------------------------------------------------- + /** + * Gets an iterator that filters another iterator. + *

      + * The returned iterator will only return objects that match the specified + * filtering predicate. + * + * @param the element type + * @param iterator the iterator to use, not null + * @param predicate the predicate to use as a filter, not null + * @return a new filtered iterator + * @throws NullPointerException if either parameter is null + */ + public static Iterator filteredIterator(final Iterator iterator, + final Predicate predicate) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + return new FilterIterator(iterator, predicate); + } + + /** + * Gets a list iterator that filters another list iterator. + *

      + * The returned iterator will only return objects that match the specified + * filtering predicate. + * + * @param the element type + * @param listIterator the list iterator to use, not null + * @param predicate the predicate to use as a filter, not null + * @return a new filtered iterator + * @throws NullPointerException if either parameter is null + */ + public static ListIterator filteredListIterator(final ListIterator listIterator, + final Predicate predicate) { + + if (listIterator == null) { + throw new NullPointerException("ListIterator must not be null"); + } + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + return new FilterListIterator(listIterator, predicate); + } + + // Looping + //----------------------------------------------------------------------- + /** + * Gets an iterator that loops continuously over the supplied collection. + *

      + * The iterator will only stop looping if the remove method is called + * enough times to empty the collection, or if the collection is empty + * to start with. + * + * @param the element type + * @param coll the collection to iterate over, not null + * @return a new looping iterator + * @throws NullPointerException if the collection is null + */ + public static ResettableIterator loopingIterator(final Collection coll) { + if (coll == null) { + throw new NullPointerException("Collection must not be null"); + } + return new LoopingIterator(coll); + } + + /** + * Gets an iterator that loops continuously over the supplied list. + *

      + * The iterator will only stop looping if the remove method is called + * enough times to empty the list, or if the list is empty to start with. + * + * @param the element type + * @param list the list to iterate over, not null + * @return a new looping iterator + * @throws NullPointerException if the list is null + * @since 3.2 + */ + public static ResettableListIterator loopingListIterator(final List list) { + if (list == null) { + throw new NullPointerException("List must not be null"); + } + return new LoopingListIterator(list); + } + + // org.w3c.dom.NodeList iterators + //----------------------------------------------------------------------- + /** + * Gets an {@link Iterator} that wraps the specified {@link NodeList}. + * The returned {@link Iterator} can be used for a single iteration. + * + * @param nodeList the node list to use, may not be null + * @return a new, single use {@link Iterator} + * @throws NullPointerException if nodeList is null + * @since 4.0 + */ + public static NodeListIterator nodeListIterator(final NodeList nodeList) { + if (nodeList == null) { + throw new NullPointerException("NodeList must not be null"); + } + return new NodeListIterator(nodeList); + } + + /** + * Gets an {@link Iterator} that wraps the specified node's childNodes. + * The returned {@link Iterator} can be used for a single iteration. + *

      + * Convenience method, allows easy iteration over NodeLists: + *

      +     *   Iterator<Node> iterator = IteratorUtils.nodeListIterator(node);
      +     *   for(Node childNode : IteratorUtils.asIterable(iterator)) {
      +     *     ...
      +     *   }
      +     * 
      + * + * @param node the node to use, may not be null + * @return a new, single use {@link Iterator} + * @throws NullPointerException if node is null + * @since 4.0 + */ + public static NodeListIterator nodeListIterator(final Node node) { + if (node == null) { + throw new NullPointerException("Node must not be null"); + } + return new NodeListIterator(node); + } + + // Peeking + //----------------------------------------------------------------------- + + /** + * Gets an iterator that supports one-element lookahead. + * + * @param the element type + * @param iterator the iterator to decorate, not null + * @return a peeking iterator + * @throws NullPointerException if the iterator is null + * @since 4.0 + */ + public static Iterator peekingIterator(final Iterator iterator) { + return PeekingIterator.peekingIterator(iterator); + } + + // Pushback + //----------------------------------------------------------------------- + + /** + * Gets an iterator that supports pushback of elements. + * + * @param the element type + * @param iterator the iterator to decorate, not null + * @return a pushback iterator + * @throws NullPointerException if the iterator is null + * @since 4.0 + */ + public static Iterator pushbackIterator(final Iterator iterator) { + return PushbackIterator.pushbackIterator(iterator); + } + + // Skipping + //----------------------------------------------------------------------- + /** + * Decorates the specified iterator to skip the first N elements. + * + * @param the element type + * @param iterator the iterator to decorate + * @param offset the first number of elements to skip + * @return a new skipping iterator + * @throws NullPointerException if the iterator is null + * @throws IllegalArgumentException if offset is negative + * @since 4.1 + */ + public static SkippingIterator skippingIterator(final Iterator iterator, long offset) { + return new SkippingIterator(iterator, offset); + } + + // Zipping + //----------------------------------------------------------------------- + /** + * Returns an iterator that interleaves elements from the decorated iterators. + * + * @param the element type + * @param a the first iterator to interleave + * @param b the second iterator to interleave + * @return an iterator, interleaving the decorated iterators + * @throws NullPointerException if any iterator is null + * @since 4.1 + */ + public static ZippingIterator zippingIterator(final Iterator a, + final Iterator b) { + return new ZippingIterator(a, b); + } + + /** + * Returns an iterator that interleaves elements from the decorated iterators. + * + * @param the element type + * @param a the first iterator to interleave + * @param b the second iterator to interleave + * @param c the third iterator to interleave + * @return an iterator, interleaving the decorated iterators + * @throws NullPointerException if any iterator is null + * @since 4.1 + */ + public static ZippingIterator zippingIterator(final Iterator a, + final Iterator b, + final Iterator c) { + return new ZippingIterator(a, b, c); + } + + /** + * Returns an iterator that interleaves elements from the decorated iterators. + * + * @param the element type + * @param iterators the array of iterators to interleave + * @return an iterator, interleaving the decorated iterators + * @throws NullPointerException if any iterator is null + * @since 4.1 + */ + public static ZippingIterator zippingIterator(final Iterator... iterators) { + return new ZippingIterator(iterators); + } + + // Views + //----------------------------------------------------------------------- + /** + * Gets an iterator that provides an iterator view of the given enumeration. + * + * @param the element type + * @param enumeration the enumeration to use, may not be null + * @return a new iterator + * @throws NullPointerException if enumeration is null + */ + public static Iterator asIterator(final Enumeration enumeration) { + if (enumeration == null) { + throw new NullPointerException("Enumeration must not be null"); + } + return new EnumerationIterator(enumeration); + } + + /** + * Gets an iterator that provides an iterator view of the given enumeration + * that will remove elements from the specified collection. + * + * @param the element type + * @param enumeration the enumeration to use, may not be null + * @param removeCollection the collection to remove elements from, may not be null + * @return a new iterator + * @throws NullPointerException if enumeration or removeCollection is null + */ + public static Iterator asIterator(final Enumeration enumeration, + final Collection removeCollection) { + if (enumeration == null) { + throw new NullPointerException("Enumeration must not be null"); + } + if (removeCollection == null) { + throw new NullPointerException("Collection must not be null"); + } + return new EnumerationIterator(enumeration, removeCollection); + } + + /** + * Gets an enumeration that wraps an iterator. + * + * @param the element type + * @param iterator the iterator to use, may not be null + * @return a new enumeration + * @throws NullPointerException if iterator is null + */ + public static Enumeration asEnumeration(final Iterator iterator) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + return new IteratorEnumeration(iterator); + } + + /** + * Gets an {@link Iterable} that wraps an iterator. The returned {@link Iterable} can be + * used for a single iteration. + * + * @param the element type + * @param iterator the iterator to use, may not be null + * @return a new, single use {@link Iterable} + * @throws NullPointerException if iterator is null + */ + public static Iterable asIterable(final Iterator iterator) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + return new IteratorIterable(iterator, false); + } + + /** + * Gets an iterable that wraps an iterator. The returned iterable can be + * used for multiple iterations. + * + * @param the element type + * @param iterator the iterator to use, may not be null + * @return a new, multiple use iterable + * @throws NullPointerException if iterator is null + */ + public static Iterable asMultipleUseIterable(final Iterator iterator) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + return new IteratorIterable(iterator, true); + } + + /** + * Gets a list iterator based on a simple iterator. + *

      + * As the wrapped Iterator is traversed, a LinkedList of its values is + * cached, permitting all required operations of ListIterator. + * + * @param the element type + * @param iterator the iterator to use, may not be null + * @return a new iterator + * @throws NullPointerException if iterator parameter is null + */ + public static ListIterator toListIterator(final Iterator iterator) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + return new ListIteratorWrapper(iterator); + } + + /** + * Gets an array based on an iterator. + *

      + * As the wrapped Iterator is traversed, an ArrayList of its values is + * created. At the end, this is converted to an array. + * + * @param iterator the iterator to use, not null + * @return an array of the iterator contents + * @throws NullPointerException if iterator parameter is null + */ + public static Object[] toArray(final Iterator iterator) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + final List list = toList(iterator, 100); + return list.toArray(); + } + + /** + * Gets an array based on an iterator. + *

      + * As the wrapped Iterator is traversed, an ArrayList of its values is + * created. At the end, this is converted to an array. + * + * @param the element type + * @param iterator the iterator to use, not null + * @param arrayClass the class of array to create + * @return an array of the iterator contents + * @throws NullPointerException if iterator parameter or arrayClass is null + * @throws ArrayStoreException if the arrayClass is invalid + */ + public static E[] toArray(final Iterator iterator, final Class arrayClass) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + if (arrayClass == null) { + throw new NullPointerException("Array class must not be null"); + } + final List list = toList(iterator, 100); + @SuppressWarnings("unchecked") + final E[] array = (E[]) Array.newInstance(arrayClass, list.size()); + return list.toArray(array); + } + + /** + * Gets a list based on an iterator. + *

      + * As the wrapped Iterator is traversed, an ArrayList of its values is + * created. At the end, the list is returned. + * + * @param the element type + * @param iterator the iterator to use, not null + * @return a list of the iterator contents + * @throws NullPointerException if iterator parameter is null + */ + public static List toList(final Iterator iterator) { + return toList(iterator, 10); + } + + /** + * Gets a list based on an iterator. + *

      + * As the wrapped Iterator is traversed, an ArrayList of its values is + * created. At the end, the list is returned. + * + * @param the element type + * @param iterator the iterator to use, not null + * @param estimatedSize the initial size of the ArrayList + * @return a list of the iterator contents + * @throws NullPointerException if iterator parameter is null + * @throws IllegalArgumentException if the size is less than 1 + */ + public static List toList(final Iterator iterator, final int estimatedSize) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + if (estimatedSize < 1) { + throw new IllegalArgumentException("Estimated size must be greater than 0"); + } + final List list = new ArrayList(estimatedSize); + while (iterator.hasNext()) { + list.add(iterator.next()); + } + return list; + } + + /** + * Gets a suitable Iterator for the given object. + *

      + * This method can handle objects as follows + *

        + *
      • null - empty iterator + *
      • Iterator - returned directly + *
      • Enumeration - wrapped + *
      • Collection - iterator from collection returned + *
      • Map - values iterator returned + *
      • Dictionary - values (elements) enumeration returned as iterator + *
      • array - iterator over array returned + *
      • object with iterator() public method accessed by reflection + *
      • object - singleton iterator + *
      • NodeList - iterator over the list + *
      • Node - iterator over the child nodes + *
      + * + * @param obj the object to convert to an iterator + * @return a suitable iterator, never null + */ + public static Iterator getIterator(final Object obj) { + if (obj == null) { + return emptyIterator(); + } + if (obj instanceof Iterator) { + return (Iterator) obj; + } + if (obj instanceof Iterable) { + return ((Iterable) obj).iterator(); + } + if (obj instanceof Object[]) { + return new ObjectArrayIterator((Object[]) obj); + } + if (obj instanceof Enumeration) { + return new EnumerationIterator((Enumeration) obj); + } + if (obj instanceof Map) { + return ((Map) obj).values().iterator(); + } + if (obj instanceof NodeList) { + return new NodeListIterator((NodeList) obj); + } + if (obj instanceof Node) { + return new NodeListIterator((Node) obj); + } + if (obj instanceof Dictionary) { + return new EnumerationIterator(((Dictionary) obj).elements()); + } else if (obj.getClass().isArray()) { + return new ArrayIterator(obj); + } + try { + final Method method = obj.getClass().getMethod("iterator", (Class[]) null); + if (Iterator.class.isAssignableFrom(method.getReturnType())) { + final Iterator it = (Iterator) method.invoke(obj, (Object[]) null); + if (it != null) { + return it; + } + } + } catch (final RuntimeException e) { // NOPMD + // ignore + } catch (final NoSuchMethodException e) { // NOPMD + // ignore + } catch (final IllegalAccessException e) { // NOPMD + // ignore + } catch (final InvocationTargetException e) { // NOPMD + // ignore + } + return singletonIterator(obj); + } + + // Utility methods + //----------------------------------------------------------------------- + + /** + * Applies the closure to each element of the provided iterator. + * + * @param the element type + * @param iterator the iterator to use, may be null + * @param closure the closure to apply to each element, may not be null + * @throws NullPointerException if closure is null + * @since 4.1 + */ + public static void forEach(final Iterator iterator, final Closure closure) { + if (closure == null) { + throw new NullPointerException("Closure must not be null"); + } + + if (iterator != null) { + while (iterator.hasNext()) { + final E element = iterator.next(); + closure.execute(element); + } + } + } + + /** + * Executes the given closure on each but the last element in the iterator. + *

      + * If the input iterator is null no change is made. + * + * @param the type of object the {@link Iterator} contains + * @param iterator the iterator to get the input from, may be null + * @param closure the closure to perform, may not be null + * @return the last element in the iterator, or null if iterator is null or empty + * @throws NullPointerException if closure is null + * @since 4.1 + */ + public static E forEachButLast(final Iterator iterator, final Closure closure) { + if (closure == null) { + throw new NullPointerException("Closure must not be null."); + } + if (iterator != null) { + while (iterator.hasNext()) { + final E element = iterator.next(); + if (iterator.hasNext()) { + closure.execute(element); + } else { + return element; + } + } + } + return null; + } + + /** + * Finds the first element in the given iterator which matches the given predicate. + *

      + * A null or empty iterator returns null. + * + * @param the element type + * @param iterator the iterator to search, may be null + * @param predicate the predicate to use, may not be null + * @return the first element of the iterator which matches the predicate or null if none could be found + * @throws NullPointerException if predicate is null + * @since 4.1 + */ + public static E find(final Iterator iterator, final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + + if (iterator != null) { + while (iterator.hasNext()) { + final E element = iterator.next(); + if (predicate.evaluate(element)) { + return element; + } + } + } + return null; + } + + /** + * Returns the index of the first element in the specified iterator that + * matches the given predicate. + *

      + * A null or empty iterator returns -1. + * + * @param the element type + * @param iterator the iterator to search, may be null + * @param predicate the predicate to use, may not be null + * @return the index of the first element which matches the predicate or -1 if none matches + * @throws NullPointerException if predicate is null + * @since 4.1 + */ + public static int indexOf(final Iterator iterator, final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + + if (iterator != null) { + for(int index = 0; iterator.hasNext(); index++) { + final E element = iterator.next(); + if (predicate.evaluate(element)) { + return index; + } + } + } + return -1; + } + + /** + * Answers true if a predicate is true for any element of the iterator. + *

      + * A null or empty iterator returns false. + * + * @param the type of object the {@link Iterator} contains + * @param iterator the {@link Iterator} to use, may be null + * @param predicate the predicate to use, may not be null + * @return true if any element of the collection matches the predicate, false otherwise + * @throws NullPointerException if predicate is null + * @since 4.1 + */ + public static boolean matchesAny(final Iterator iterator, final Predicate predicate) { + return indexOf(iterator, predicate) != -1; + } + + /** + * Answers true if a predicate is true for every element of an iterator. + *

      + * A null or empty iterator returns true. + * + * @param the type of object the {@link Iterator} contains + * @param iterator the {@link Iterator} to use, may be null + * @param predicate the predicate to use, may not be null + * @return true if every element of the collection matches the predicate or if the + * collection is empty, false otherwise + * @throws NullPointerException if predicate is null + * @since 4.1 + */ + public static boolean matchesAll(final Iterator iterator, final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + + if (iterator != null) { + while (iterator.hasNext()) { + final E element = iterator.next(); + if (!predicate.evaluate(element)) { + return false; + } + } + } + return true; + } + + /** + * Checks if the given iterator is empty. + *

      + * A null or empty iterator returns true. + * + * @param iterator the {@link Iterator} to use, may be null + * @return true if the iterator is exhausted or null, false otherwise + * @since 4.1 + */ + public static boolean isEmpty(final Iterator iterator) { + return iterator == null || !iterator.hasNext(); + } + + /** + * Checks if the object is contained in the given iterator. + *

      + * A null or empty iterator returns false. + * + * @param the type of object the {@link Iterator} contains + * @param iterator the iterator to check, may be null + * @param object the object to check + * @return true if the object is contained in the iterator, false otherwise + * @since 4.1 + */ + public static boolean contains(final Iterator iterator, final Object object) { + return matchesAny(iterator, EqualPredicate.equalPredicate(object)); + } + + /** + * Returns the index-th value in {@link Iterator}, throwing + * IndexOutOfBoundsException if there is no such element. + *

      + * The Iterator is advanced to index (or to the end, if + * index exceeds the number of entries) as a side effect of this method. + * + * @param the type of object in the {@link Iterator} + * @param iterator the iterator to get a value from + * @param index the index to get + * @return the object at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + * @since 4.1 + */ + public static E get(final Iterator iterator, final int index) { + int i = index; + CollectionUtils.checkIndexBounds(i); + while (iterator.hasNext()) { + i--; + if (i == -1) { + return iterator.next(); + } + iterator.next(); + } + throw new IndexOutOfBoundsException("Entry does not exist: " + i); + } + + /** + * Returns the number of elements contained in the given iterator. + *

      + * A null or empty iterator returns {@code 0}. + * + * @param iterator the iterator to check, may be null + * @return the number of elements contained in the iterator + * @since 4.1 + */ + public static int size(final Iterator iterator) { + int size = 0; + if (iterator != null) { + while (iterator.hasNext()) { + iterator.next(); + size++; + } + } + return size; + } + + /** + * Returns a string representation of the elements of the specified iterator. + *

      + * The string representation consists of a list of the iterator's elements, + * enclosed in square brackets ({@code "[]"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). Elements are + * converted to strings as by {@code String.valueOf(Object)}. + * + * @param the element type + * @param iterator the iterator to convert to a string, may be null + * @return a string representation of {@code iterator} + * @since 4.1 + */ + public static String toString(final Iterator iterator) { + return toString(iterator, TransformerUtils.stringValueTransformer(), + DEFAULT_TOSTRING_DELIMITER, DEFAULT_TOSTRING_PREFIX, + DEFAULT_TOSTRING_SUFFIX); + } + + /** + * Returns a string representation of the elements of the specified iterator. + *

      + * The string representation consists of a list of the iterable's elements, + * enclosed in square brackets ({@code "[]"}). Adjacent elements are separated + * by the characters {@code ", "} (a comma followed by a space). Elements are + * converted to strings as by using the provided {@code transformer}. + * + * @param the element type + * @param iterator the iterator to convert to a string, may be null + * @param transformer the transformer used to get a string representation of an element + * @return a string representation of {@code iterator} + * @throws NullPointerException if {@code transformer} is null + * @since 4.1 + */ + public static String toString(final Iterator iterator, + final Transformer transformer) { + return toString(iterator, transformer, DEFAULT_TOSTRING_DELIMITER, + DEFAULT_TOSTRING_PREFIX, DEFAULT_TOSTRING_SUFFIX); + } + + /** + * Returns a string representation of the elements of the specified iterator. + *

      + * The string representation consists of a list of the iterator's elements, + * enclosed by the provided {@code prefix} and {@code suffix}. Adjacent elements + * are separated by the provided {@code delimiter}. Elements are converted to + * strings as by using the provided {@code transformer}. + * + * @param the element type + * @param iterator the iterator to convert to a string, may be null + * @param transformer the transformer used to get a string representation of an element + * @param delimiter the string to delimit elements + * @param prefix the prefix, prepended to the string representation + * @param suffix the suffix, appended to the string representation + * @return a string representation of {@code iterator} + * @throws NullPointerException if either transformer, delimiter, prefix or suffix is null + * @since 4.1 + */ + public static String toString(final Iterator iterator, + final Transformer transformer, + final String delimiter, + final String prefix, + final String suffix) { + if (transformer == null) { + throw new NullPointerException("transformer may not be null"); + } + if (delimiter == null) { + throw new NullPointerException("delimiter may not be null"); + } + if (prefix == null) { + throw new NullPointerException("prefix may not be null"); + } + if (suffix == null) { + throw new NullPointerException("suffix may not be null"); + } + final StringBuilder stringBuilder = new StringBuilder(prefix); + if (iterator != null) { + while (iterator.hasNext()) { + final E element = iterator.next(); + stringBuilder.append(transformer.transform(element)); + stringBuilder.append(delimiter); + } + if(stringBuilder.length() > prefix.length()) { + stringBuilder.setLength(stringBuilder.length() - delimiter.length()); + } + } + stringBuilder.append(suffix); + return stringBuilder.toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/KeyValue.java b/fine-commons-collections4/src/org/apache/commons/collections4/KeyValue.java new file mode 100644 index 000000000..81d0a11fc --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/KeyValue.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Defines a simple key value pair. + *

      + * A Map Entry has considerable additional semantics over and above a simple + * key-value pair. This interface defines the minimum key value, with just the + * two get methods. + * + * @param the type of the key + * @param the type of the value + * @since 3.0 + * @version $Id: KeyValue.java 1477779 2013-04-30 18:55:24Z tn $ + */ +public interface KeyValue { + + /** + * Gets the key from the pair. + * + * @return the key + */ + K getKey(); + + /** + * Gets the value from the pair. + * + * @return the value + */ + V getValue(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/ListUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/ListUtils.java new file mode 100644 index 000000000..d9a6be9af --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/ListUtils.java @@ -0,0 +1,699 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.collections4.bag.HashBag; +import org.apache.commons.collections4.functors.DefaultEquator; +import org.apache.commons.collections4.list.FixedSizeList; +import org.apache.commons.collections4.list.LazyList; +import org.apache.commons.collections4.list.PredicatedList; +import org.apache.commons.collections4.list.TransformedList; +import org.apache.commons.collections4.list.UnmodifiableList; +import org.apache.commons.collections4.sequence.CommandVisitor; +import org.apache.commons.collections4.sequence.EditScript; +import org.apache.commons.collections4.sequence.SequencesComparator; + +/** + * Provides utility methods and decorators for {@link List} instances. + * + * @since 1.0 + * @version $Id: ListUtils.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class ListUtils { + + /** + * ListUtils should not normally be instantiated. + */ + private ListUtils() {} + + //----------------------------------------------------------------------- + + /** + * Returns an immutable empty list if the argument is null, + * or the argument itself otherwise. + * + * @param the element type + * @param list the list, possibly null + * @return an empty list if the argument is null + */ + public static List emptyIfNull(final List list) { + return list == null ? Collections.emptyList() : list; + } + + /** + * Returns either the passed in list, or if the list is {@code null}, + * the value of {@code defaultList}. + * + * @param the element type + * @param list the list, possibly {@code null} + * @param defaultList the returned values if list is {@code null} + * @return an empty list if the argument is null + * @since 4.0 + */ + public static List defaultIfNull(final List list, final List defaultList) { + return list == null ? defaultList : list; + } + + /** + * Returns a new list containing all elements that are contained in + * both given lists. + * + * @param the element type + * @param list1 the first list + * @param list2 the second list + * @return the intersection of those two lists + * @throws NullPointerException if either list is null + */ + public static List intersection(final List list1, final List list2) { + final List result = new ArrayList(); + + List smaller = list1; + List larger = list2; + if (list1.size() > list2.size()) { + smaller = list2; + larger = list1; + } + + final HashSet hashSet = new HashSet(smaller); + + for (final E e : larger) { + if (hashSet.contains(e)) { + result.add(e); + hashSet.remove(e); + } + } + return result; + } + + /** + * Subtracts all elements in the second list from the first list, + * placing the results in a new list. + *

      + * This differs from {@link List#removeAll(Collection)} in that + * cardinality is respected; if list1 contains two + * occurrences of null and list2 only + * contains one occurrence, then the returned list will still contain + * one occurrence. + * + * @param the element type + * @param list1 the list to subtract from + * @param list2 the list to subtract + * @return a new list containing the results + * @throws NullPointerException if either list is null + */ + public static List subtract(final List list1, final List list2) { + final ArrayList result = new ArrayList(); + final HashBag bag = new HashBag(list2); + for (final E e : list1) { + if (!bag.remove(e, 1)) { + result.add(e); + } + } + return result; + } + + /** + * Returns the sum of the given lists. This is their intersection + * subtracted from their union. + * + * @param the element type + * @param list1 the first list + * @param list2 the second list + * @return a new list containing the sum of those lists + * @throws NullPointerException if either list is null + */ + public static List sum(final List list1, final List list2) { + return subtract(union(list1, list2), intersection(list1, list2)); + } + + /** + * Returns a new list containing the second list appended to the + * first list. The {@link List#addAll(Collection)} operation is + * used to append the two given lists into a new list. + * + * @param the element type + * @param list1 the first list + * @param list2 the second list + * @return a new list containing the union of those lists + * @throws NullPointerException if either list is null + */ + public static List union(final List list1, final List list2) { + final ArrayList result = new ArrayList(list1); + result.addAll(list2); + return result; + } + + /** + * Selects all elements from input collection which match the given + * predicate into an output list. + *

      + * A null predicate matches no elements. + * + * @param the element type + * @param inputCollection the collection to get the input from, may not be null + * @param predicate the predicate to use, may be null + * @return the elements matching the predicate (new list) + * @throws NullPointerException if the input list is null + * + * @since 4.0 + * @see CollectionUtils#select(Iterable, Predicate) + */ + public static List select(final Collection inputCollection, + final Predicate predicate) { + return CollectionUtils.select(inputCollection, predicate, new ArrayList(inputCollection.size())); + } + + /** + * Selects all elements from inputCollection which don't match the given + * predicate into an output collection. + *

      + * If the input predicate is null, the result is an empty list. + * + * @param the element type + * @param inputCollection the collection to get the input from, may not be null + * @param predicate the predicate to use, may be null + * @return the elements not matching the predicate (new list) + * @throws NullPointerException if the input collection is null + * + * @since 4.0 + * @see CollectionUtils#selectRejected(Iterable, Predicate) + */ + public static List selectRejected(final Collection inputCollection, + final Predicate predicate) { + return CollectionUtils.selectRejected(inputCollection, predicate, new ArrayList(inputCollection.size())); + } + + /** + * Tests two lists for value-equality as per the equality contract in + * {@link java.util.List#equals(java.lang.Object)}. + *

      + * This method is useful for implementing List when you cannot + * extend AbstractList. The method takes Collection instances to enable other + * collection types to use the List implementation algorithm. + *

      + * The relevant text (slightly paraphrased as this is a static method) is: + *

      + * Compares the two list objects for equality. Returns + * {@code true} if and only if both + * lists have the same size, and all corresponding pairs of elements in + * the two lists are equal. (Two elements {@code e1} and + * {@code e2} are equal if (e1==null ? e2==null : + * e1.equals(e2)).) In other words, two lists are defined to be + * equal if they contain the same elements in the same order. This + * definition ensures that the equals method works properly across + * different implementations of the {@code List} interface. + *
      + * + * Note: The behaviour of this method is undefined if the lists are + * modified during the equals comparison. + * + * @see java.util.List + * @param list1 the first list, may be null + * @param list2 the second list, may be null + * @return whether the lists are equal by value comparison + */ + public static boolean isEqualList(final Collection list1, final Collection list2) { + if (list1 == list2) { + return true; + } + if (list1 == null || list2 == null || list1.size() != list2.size()) { + return false; + } + + final Iterator it1 = list1.iterator(); + final Iterator it2 = list2.iterator(); + Object obj1 = null; + Object obj2 = null; + + while (it1.hasNext() && it2.hasNext()) { + obj1 = it1.next(); + obj2 = it2.next(); + + if (!(obj1 == null ? obj2 == null : obj1.equals(obj2))) { + return false; + } + } + + return !(it1.hasNext() || it2.hasNext()); + } + + /** + * Generates a hash code using the algorithm specified in + * {@link java.util.List#hashCode()}. + *

      + * This method is useful for implementing List when you cannot + * extend AbstractList. The method takes Collection instances to enable other + * collection types to use the List implementation algorithm. + * + * @see java.util.List#hashCode() + * @param list the list to generate the hashCode for, may be null + * @return the hash code + */ + public static int hashCodeForList(final Collection list) { + if (list == null) { + return 0; + } + int hashCode = 1; + final Iterator it = list.iterator(); + + while (it.hasNext()) { + final Object obj = it.next(); + hashCode = 31 * hashCode + (obj == null ? 0 : obj.hashCode()); + } + return hashCode; + } + + //----------------------------------------------------------------------- + /** + * Returns a List containing all the elements in collection + * that are also in retain. The cardinality of an element e + * in the returned list is the same as the cardinality of e + * in collection unless retain does not contain e, in which + * case the cardinality is zero. This method is useful if you do not wish to modify + * the collection c and thus cannot call collection.retainAll(retain);. + *

      + * This implementation iterates over collection, checking each element in + * turn to see if it's contained in retain. If it's contained, it's added + * to the returned list. As a consequence, it is advised to use a collection type for + * retain that provides a fast (e.g. O(1)) implementation of + * {@link Collection#contains(Object)}. + * + * @param the element type + * @param collection the collection whose contents are the target of the #retailAll operation + * @param retain the collection containing the elements to be retained in the returned collection + * @return a List containing all the elements of c + * that occur at least once in retain. + * @throws NullPointerException if either parameter is null + * @since 3.2 + */ + public static List retainAll(final Collection collection, final Collection retain) { + final List list = new ArrayList(Math.min(collection.size(), retain.size())); + + for (final E obj : collection) { + if (retain.contains(obj)) { + list.add(obj); + } + } + return list; + } + + /** + * Removes the elements in remove from collection. That is, this + * method returns a list containing all the elements in collection + * that are not in remove. The cardinality of an element e + * in the returned collection is the same as the cardinality of e + * in collection unless remove contains e, in which + * case the cardinality is zero. This method is useful if you do not wish to modify + * collection and thus cannot call collection.removeAll(remove);. + *

      + * This implementation iterates over collection, checking each element in + * turn to see if it's contained in remove. If it's not contained, it's added + * to the returned list. As a consequence, it is advised to use a collection type for + * remove that provides a fast (e.g. O(1)) implementation of + * {@link Collection#contains(Object)}. + * + * @param the element type + * @param collection the collection from which items are removed (in the returned collection) + * @param remove the items to be removed from the returned collection + * @return a List containing all the elements of c except + * any elements that also occur in remove. + * @throws NullPointerException if either parameter is null + * @since 3.2 + */ + public static List removeAll(final Collection collection, final Collection remove) { + final List list = new ArrayList(); + for (final E obj : collection) { + if (!remove.contains(obj)) { + list.add(obj); + } + } + return list; + } + + //----------------------------------------------------------------------- + /** + * Returns a synchronized list backed by the given list. + *

      + * You must manually synchronize on the returned list's iterator to + * avoid non-deterministic behavior: + * + *

      +     * List list = ListUtils.synchronizedList(myList);
      +     * synchronized (list) {
      +     *     Iterator i = list.iterator();
      +     *     while (i.hasNext()) {
      +     *         process (i.next());
      +     *     }
      +     * }
      +     * 
      + * + * This method is just a wrapper for {@link Collections#synchronizedList(List)}. + * + * @param the element type + * @param list the list to synchronize, must not be null + * @return a synchronized list backed by the given list + * @throws NullPointerException if the list is null + */ + public static List synchronizedList(final List list) { + return Collections.synchronizedList(list); + } + + /** + * Returns an unmodifiable list backed by the given list. + *

      + * This method uses the implementation in the decorators subpackage. + * + * @param the element type + * @param list the list to make unmodifiable, must not be null + * @return an unmodifiable list backed by the given list + * @throws NullPointerException if the list is null + */ + public static List unmodifiableList(final List list) { + return UnmodifiableList.unmodifiableList(list); + } + + /** + * Returns a predicated (validating) list backed by the given list. + *

      + * Only objects that pass the test in the given predicate can be added to the list. + * Trying to add an invalid object results in an IllegalArgumentException. + * It is important not to use the original list after invoking this method, + * as it is a backdoor for adding invalid objects. + * + * @param the element type + * @param list the list to predicate, must not be null + * @param predicate the predicate for the list, must not be null + * @return a predicated list backed by the given list + * @throws NullPointerException if the List or Predicate is null + */ + public static List predicatedList(final List list, final Predicate predicate) { + return PredicatedList.predicatedList(list, predicate); + } + + /** + * Returns a transformed list backed by the given list. + *

      + * This method returns a new list (decorating the specified list) that + * will transform any new entries added to it. + * Existing entries in the specified list will not be transformed. + *

      + * Each object is passed through the transformer as it is added to the + * List. It is important not to use the original list after invoking this + * method, as it is a backdoor for adding untransformed objects. + *

      + * Existing entries in the specified list will not be transformed. + * If you want that behaviour, see {@link TransformedList#transformedList}. + * + * @param the element type + * @param list the list to predicate, must not be null + * @param transformer the transformer for the list, must not be null + * @return a transformed list backed by the given list + * @throws NullPointerException if the List or Transformer is null + */ + public static List transformedList(final List list, + final Transformer transformer) { + return TransformedList.transformingList(list, transformer); + } + + /** + * Returns a "lazy" list whose elements will be created on demand. + *

      + * When the index passed to the returned list's {@link List#get(int) get} + * method is greater than the list's size, then the factory will be used + * to create a new object and that object will be inserted at that index. + *

      + * For instance: + * + *

      +     * Factory<Date> factory = new Factory<Date>() {
      +     *     public Date create() {
      +     *         return new Date();
      +     *     }
      +     * }
      +     * List<Date> lazy = ListUtils.lazyList(new ArrayList<Date>(), factory);
      +     * Date date = lazy.get(3);
      +     * 
      + * + * After the above code is executed, date will refer to + * a new Date instance. Furthermore, that Date + * instance is the fourth element in the list. The first, second, + * and third element are all set to null. + * + * @param the element type + * @param list the list to make lazy, must not be null + * @param factory the factory for creating new objects, must not be null + * @return a lazy list backed by the given list + * @throws NullPointerException if the List or Factory is null + */ + public static List lazyList(final List list, final Factory factory) { + return LazyList.lazyList(list, factory); + } + + /** + * Returns a fixed-sized list backed by the given list. + * Elements may not be added or removed from the returned list, but + * existing elements can be changed (for instance, via the + * {@link List#set(int, Object)} method). + * + * @param the element type + * @param list the list whose size to fix, must not be null + * @return a fixed-size list backed by that list + * @throws NullPointerException if the List is null + */ + public static List fixedSizeList(final List list) { + return FixedSizeList.fixedSizeList(list); + } + + //----------------------------------------------------------------------- + /** + * Finds the first index in the given List which matches the given predicate. + *

      + * If the input List or predicate is null, or no element of the List + * matches the predicate, -1 is returned. + * + * @param the element type + * @param list the List to search, may be null + * @param predicate the predicate to use, may be null + * @return the first index of an Object in the List which matches the predicate or -1 if none could be found + */ + public static int indexOf(final List list, final Predicate predicate) { + if (list != null && predicate != null) { + for (int i = 0; i < list.size(); i++) { + final E item = list.get(i); + if (predicate.evaluate(item)) { + return i; + } + } + } + return -1; + } + + //----------------------------------------------------------------------- + /** + * Returns the longest common subsequence (LCS) of two sequences (lists). + * + * @param the element type + * @param a the first list + * @param b the second list + * @return the longest common subsequence + * @throws NullPointerException if either list is {@code null} + * @since 4.0 + */ + public static List longestCommonSubsequence(final List a, final List b) { + return longestCommonSubsequence( a, b, DefaultEquator.defaultEquator() ); + } + + /** + * Returns the longest common subsequence (LCS) of two sequences (lists). + * + * @param the element type + * @param a the first list + * @param b the second list + * @param equator the equator used to test object equality + * @return the longest common subsequence + * @throws NullPointerException if either list or the equator is {@code null} + * @since 4.0 + */ + public static List longestCommonSubsequence(final List a, final List b, + final Equator equator) { + if (a == null || b == null) { + throw new NullPointerException("List must not be null"); + } + if (equator == null) { + throw new NullPointerException("Equator must not be null"); + } + + final SequencesComparator comparator = new SequencesComparator(a, b, equator); + final EditScript script = comparator.getScript(); + final LcsVisitor visitor = new LcsVisitor(); + script.visit(visitor); + return visitor.getSubSequence(); + } + + /** + * Returns the longest common subsequence (LCS) of two {@link CharSequence} objects. + *

      + * This is a convenience method for using {@link #longestCommonSubsequence(List, List)} + * with {@link CharSequence} instances. + * + * @param a the first sequence + * @param b the second sequence + * @return the longest common subsequence as {@link String} + * @throws NullPointerException if either sequence is {@code null} + * @since 4.0 + */ + public static String longestCommonSubsequence(final CharSequence a, final CharSequence b) { + if (a == null || b == null) { + throw new NullPointerException("CharSequence must not be null"); + } + final List lcs = longestCommonSubsequence(new CharSequenceAsList( a ), new CharSequenceAsList( b )); + final StringBuilder sb = new StringBuilder(); + for ( Character ch : lcs ) { + sb.append(ch); + } + return sb.toString(); + } + + /** + * A helper class used to construct the longest common subsequence. + */ + private static final class LcsVisitor implements CommandVisitor { + private ArrayList sequence; + + public LcsVisitor() { + sequence = new ArrayList(); + } + + public void visitInsertCommand(final E object) {} + + public void visitDeleteCommand(final E object) {} + + public void visitKeepCommand(final E object) { + sequence.add(object); + } + + public List getSubSequence() { + return sequence; + } + } + + /** + * A simple wrapper to use a CharSequence as List. + */ + private static final class CharSequenceAsList extends AbstractList { + + private final CharSequence sequence; + + public CharSequenceAsList(final CharSequence sequence) { + this.sequence = sequence; + } + + @Override + public Character get( int index ) { + return Character.valueOf(sequence.charAt( index )); + } + + @Override + public int size() { + return sequence.length(); + } + + } + + //----------------------------------------------------------------------- + /** + * Returns consecutive {@link List#subList(int, int) sublists} of a + * list, each of the same size (the final list may be smaller). For example, + * partitioning a list containing {@code [a, b, c, d, e]} with a partition + * size of 3 yields {@code [[a, b, c], [d, e]]} -- an outer list containing + * two inner lists of three and two elements, all in the original order. + *

      + * The outer list is unmodifiable, but reflects the latest state of the + * source list. The inner lists are sublist views of the original list, + * produced on demand using {@link List#subList(int, int)}, and are subject + * to all the usual caveats about modification as explained in that API. + *

      + * Adapted from http://code.google.com/p/guava-libraries/ + * + * @param the element type + * @param list the list to return consecutive sublists of + * @param size the desired size of each sublist (the last may be smaller) + * @return a list of consecutive sublists + * @throws NullPointerException if list is null + * @throws IllegalArgumentException if size is not strictly positive + * @since 4.0 + */ + public static List> partition(final List list, final int size) { + if (list == null) { + throw new NullPointerException("List must not be null"); + } + if (size <= 0) { + throw new IllegalArgumentException("Size must be greater than 0"); + } + return new Partition(list, size); + } + + /** + * Provides a partition view on a {@link List}. + * @since 4.0 + */ + private static class Partition extends AbstractList> { + private final List list; + private final int size; + + private Partition(final List list, final int size) { + this.list = list; + this.size = size; + } + + @Override + public List get(final int index) { + final int listSize = size(); + if (listSize < 0) { + throw new IllegalArgumentException("negative size: " + listSize); + } + if (index < 0) { + throw new IndexOutOfBoundsException("Index " + index + " must not be negative"); + } + if (index >= listSize) { + throw new IndexOutOfBoundsException("Index " + index + " must be less than size " + + listSize); + } + final int start = index * size; + final int end = Math.min(start + size, list.size()); + return list.subList(start, end); + } + + @Override + public int size() { + return (list.size() + size - 1) / size; + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/ListValuedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/ListValuedMap.java new file mode 100644 index 000000000..0f8d6efb8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/ListValuedMap.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.List; + +/** + * Defines a map that holds a list of values against each key. + *

      + * A {@code ListValuedMap} is a Map with slightly different semantics: + *

        + *
      • Putting a value into the map will add the value to a {@link List} at that key.
      • + *
      • Getting a value will return a {@link List}, holding all the values put to that key.
      • + *
      + * + * @since 4.1 + * @version $Id: ListValuedMap.java 1685299 2015-06-13 18:27:11Z tn $ + */ +public interface ListValuedMap extends MultiValuedMap { + + /** + * Gets the list of values associated with the specified key. + *

      + * This method will return an empty list if + * {@link #containsKey(Object)} returns {@code false}. Changes to the + * returned list will update the underlying {@code ListValuedMap} and + * vice-versa. + * + * @param key the key to retrieve + * @return the {@code List} of values, implementations should return an + * empty {@code List} for no mapping + * @throws NullPointerException if the key is null and null keys are invalid + */ + @Override + List get(K key); + + /** + * Removes all values associated with the specified key. + *

      + * The returned list may be modifiable, but updates will not be + * propagated to this list-valued map. In case no mapping was stored for the + * specified key, an empty, unmodifiable list will be returned. + * + * @param key the key to remove values from + * @return the {@code List} of values removed, implementations + * typically return an empty, unmodifiable {@code List} for no mapping found + * @throws UnsupportedOperationException if the map is unmodifiable + * @throws NullPointerException if the key is null and null keys are invalid + */ + @Override + List remove(Object key); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/MapIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/MapIterator.java new file mode 100644 index 000000000..fc9a9a6f4 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/MapIterator.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Iterator; + +/** + * Defines an iterator that operates over a Map. + *

      + * This iterator is a special version designed for maps. It can be more + * efficient to use this rather than an entry set iterator where the option + * is available, and it is certainly more convenient. + *

      + * A map that provides this interface may not hold the data internally using + * Map Entry objects, thus this interface can avoid lots of object creation. + *

      + * In use, this iterator iterates through the keys in the map. After each call + * to next(), the getValue() method provides direct + * access to the value. The value can also be set using setValue(). + *

      + * MapIterator it = map.mapIterator();
      + * while (it.hasNext()) {
      + *   String key = it.next();
      + *   Integer value = it.getValue();
      + *   it.setValue(value + 1);
      + * }
      + * 
      + * + * @param the type of the keys in the map + * @param the type of the values in the map + * @since 3.0 + * @version $Id: MapIterator.java 1469004 2013-04-17 17:37:03Z tn $ + */ +public interface MapIterator extends Iterator { + + /** + * Checks to see if there are more entries still to be iterated. + * + * @return true if the iterator has more elements + */ + boolean hasNext(); + + /** + * Gets the next key from the Map. + * + * @return the next key in the iteration + * @throws java.util.NoSuchElementException if the iteration is finished + */ + K next(); + + //----------------------------------------------------------------------- + /** + * Gets the current key, which is the key returned by the last call + * to next(). + * + * @return the current key + * @throws IllegalStateException if next() has not yet been called + */ + K getKey(); + + /** + * Gets the current value, which is the value associated with the last key + * returned by next(). + * + * @return the current value + * @throws IllegalStateException if next() has not yet been called + */ + V getValue(); + + //----------------------------------------------------------------------- + /** + * Removes the last returned key from the underlying Map (optional operation). + *

      + * This method can be called once per call to next(). + * + * @throws UnsupportedOperationException if remove is not supported by the map + * @throws IllegalStateException if next() has not yet been called + * @throws IllegalStateException if remove() has already been called + * since the last call to next() + */ + void remove(); + + /** + * Sets the value associated with the current key (optional operation). + * + * @param value the new value + * @return the previous value + * @throws UnsupportedOperationException if setValue is not supported by the map + * @throws IllegalStateException if next() has not yet been called + * @throws IllegalStateException if remove() has been called since the + * last call to next() + */ + V setValue(V value); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/MapUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/MapUtils.java new file mode 100644 index 000000000..48a0f8c3a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/MapUtils.java @@ -0,0 +1,1797 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.io.PrintStream; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.commons.collections4.map.AbstractMapDecorator; +import org.apache.commons.collections4.map.AbstractSortedMapDecorator; +import org.apache.commons.collections4.map.FixedSizeMap; +import org.apache.commons.collections4.map.FixedSizeSortedMap; +import org.apache.commons.collections4.map.LazyMap; +import org.apache.commons.collections4.map.LazySortedMap; +import org.apache.commons.collections4.map.ListOrderedMap; +import org.apache.commons.collections4.map.MultiValueMap; +import org.apache.commons.collections4.map.PredicatedMap; +import org.apache.commons.collections4.map.PredicatedSortedMap; +import org.apache.commons.collections4.map.TransformedMap; +import org.apache.commons.collections4.map.TransformedSortedMap; +import org.apache.commons.collections4.map.UnmodifiableMap; +import org.apache.commons.collections4.map.UnmodifiableSortedMap; + +/** + * Provides utility methods and decorators for + * {@link Map} and {@link SortedMap} instances. + *

      + * It contains various type safe methods + * as well as other useful features like deep copying. + *

      + * It also provides the following decorators: + * + *

        + *
      • {@link #fixedSizeMap(Map)} + *
      • {@link #fixedSizeSortedMap(SortedMap)} + *
      • {@link #lazyMap(Map,Factory)} + *
      • {@link #lazyMap(Map,Transformer)} + *
      • {@link #lazySortedMap(SortedMap,Factory)} + *
      • {@link #lazySortedMap(SortedMap,Transformer)} + *
      • {@link #predicatedMap(Map,Predicate,Predicate)} + *
      • {@link #predicatedSortedMap(SortedMap,Predicate,Predicate)} + *
      • {@link #transformedMap(Map, Transformer, Transformer)} + *
      • {@link #transformedSortedMap(SortedMap, Transformer, Transformer)} + *
      • {@link #multiValueMap( Map )} + *
      • {@link #multiValueMap( Map, Class )} + *
      • {@link #multiValueMap( Map, Factory )} + *
      + * + * @since 1.0 + * @version $Id: MapUtils.java 1686921 2015-06-22 19:48:47Z tn $ + */ +@SuppressWarnings("deprecation") +public class MapUtils { + + /** + * An empty unmodifiable sorted map. + * This is not provided in the JDK. + */ + @SuppressWarnings("rawtypes") + public static final SortedMap EMPTY_SORTED_MAP = + UnmodifiableSortedMap.unmodifiableSortedMap(new TreeMap()); + + /** + * String used to indent the verbose and debug Map prints. + */ + private static final String INDENT_STRING = " "; + + /** + * MapUtils should not normally be instantiated. + */ + private MapUtils() {} + + // Type safe getters + //------------------------------------------------------------------------- + /** + * Gets from a Map in a null-safe manner. + * + * @param the key type + * @param the value type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map, null if null map input + */ + public static V getObject(final Map map, final K key) { + if (map != null) { + return map.get(key); + } + return null; + } + + /** + * Gets a String from a Map in a null-safe manner. + *

      + * The String is obtained via toString. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a String, null if null map input + */ + public static String getString(final Map map, final K key) { + if (map != null) { + final Object answer = map.get(key); + if (answer != null) { + return answer.toString(); + } + } + return null; + } + + /** + * Gets a Boolean from a Map in a null-safe manner. + *

      + * If the value is a Boolean it is returned directly. + * If the value is a String and it equals 'true' ignoring case + * then true is returned, otherwise false. + * If the value is a Number an integer zero value returns + * false and non-zero returns true. + * Otherwise, null is returned. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a Boolean, null if null map input + */ + public static Boolean getBoolean(final Map map, final K key) { + if (map != null) { + final Object answer = map.get(key); + if (answer != null) { + if (answer instanceof Boolean) { + return (Boolean) answer; + } + if (answer instanceof String) { + return Boolean.valueOf((String) answer); + } + if (answer instanceof Number) { + final Number n = (Number) answer; + return n.intValue() != 0 ? Boolean.TRUE : Boolean.FALSE; + } + } + } + return null; + } + + /** + * Gets a Number from a Map in a null-safe manner. + *

      + * If the value is a Number it is returned directly. + * If the value is a String it is converted using + * {@link NumberFormat#parse(String)} on the system default formatter + * returning null if the conversion fails. + * Otherwise, null is returned. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a Number, null if null map input + */ + public static Number getNumber(final Map map, final K key) { + if (map != null) { + final Object answer = map.get(key); + if (answer != null) { + if (answer instanceof Number) { + return (Number) answer; + } + if (answer instanceof String) { + try { + final String text = (String) answer; + return NumberFormat.getInstance().parse(text); + } catch (final ParseException e) { // NOPMD + // failure means null is returned + } + } + } + } + return null; + } + + /** + * Gets a Byte from a Map in a null-safe manner. + *

      + * The Byte is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a Byte, null if null map input + */ + public static Byte getByte(final Map map, final K key) { + final Number answer = getNumber(map, key); + if (answer == null) { + return null; + } + if (answer instanceof Byte) { + return (Byte) answer; + } + return Byte.valueOf(answer.byteValue()); + } + + /** + * Gets a Short from a Map in a null-safe manner. + *

      + * The Short is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a Short, null if null map input + */ + public static Short getShort(final Map map, final K key) { + final Number answer = getNumber(map, key); + if (answer == null) { + return null; + } + if (answer instanceof Short) { + return (Short) answer; + } + return Short.valueOf(answer.shortValue()); + } + + /** + * Gets a Integer from a Map in a null-safe manner. + *

      + * The Integer is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a Integer, null if null map input + */ + public static Integer getInteger(final Map map, final K key) { + final Number answer = getNumber(map, key); + if (answer == null) { + return null; + } + if (answer instanceof Integer) { + return (Integer) answer; + } + return Integer.valueOf(answer.intValue()); + } + + /** + * Gets a Long from a Map in a null-safe manner. + *

      + * The Long is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a Long, null if null map input + */ + public static Long getLong(final Map map, final K key) { + final Number answer = getNumber(map, key); + if (answer == null) { + return null; + } + if (answer instanceof Long) { + return (Long) answer; + } + return Long.valueOf(answer.longValue()); + } + + /** + * Gets a Float from a Map in a null-safe manner. + *

      + * The Float is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a Float, null if null map input + */ + public static Float getFloat(final Map map, final K key) { + final Number answer = getNumber(map, key); + if (answer == null) { + return null; + } + if (answer instanceof Float) { + return (Float) answer; + } + return Float.valueOf(answer.floatValue()); + } + + /** + * Gets a Double from a Map in a null-safe manner. + *

      + * The Double is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a Double, null if null map input + */ + public static Double getDouble(final Map map, final K key) { + final Number answer = getNumber(map, key); + if (answer == null) { + return null; + } + if (answer instanceof Double) { + return (Double) answer; + } + return Double.valueOf(answer.doubleValue()); + } + + /** + * Gets a Map from a Map in a null-safe manner. + *

      + * If the value returned from the specified map is not a Map then + * null is returned. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a Map, null if null map input + */ + public static Map getMap(final Map map, final K key) { + if (map != null) { + final Object answer = map.get(key); + if (answer != null && answer instanceof Map) { + return (Map) answer; + } + } + return null; + } + + // Type safe getters with default values + //------------------------------------------------------------------------- + /** + * Looks up the given key in the given map, converting null into the + * given default value. + * + * @param the key type + * @param the value type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null + * @return the value in the map, or defaultValue if the original value + * is null or the map is null + */ + public static V getObject(final Map map, final K key, final V defaultValue) { + if (map != null) { + final V answer = map.get(key); + if (answer != null) { + return answer; + } + } + return defaultValue; + } + + /** + * Looks up the given key in the given map, converting the result into + * a string, using the default value if the the conversion fails. + * + * @param the key type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null or if the + * conversion fails + * @return the value in the map as a string, or defaultValue if the + * original value is null, the map is null or the string conversion fails + */ + public static String getString(final Map map, final K key, final String defaultValue) { + String answer = getString(map, key); + if (answer == null) { + answer = defaultValue; + } + return answer; + } + + /** + * Looks up the given key in the given map, converting the result into + * a boolean, using the default value if the the conversion fails. + * + * @param the key type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null or if the + * conversion fails + * @return the value in the map as a boolean, or defaultValue if the + * original value is null, the map is null or the boolean conversion fails + */ + public static Boolean getBoolean(final Map map, final K key, final Boolean defaultValue) { + Boolean answer = getBoolean(map, key); + if (answer == null) { + answer = defaultValue; + } + return answer; + } + + /** + * Looks up the given key in the given map, converting the result into + * a number, using the default value if the the conversion fails. + * + * @param the key type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null or if the + * conversion fails + * @return the value in the map as a number, or defaultValue if the + * original value is null, the map is null or the number conversion fails + */ + public static Number getNumber(final Map map, final K key, final Number defaultValue) { + Number answer = getNumber(map, key); + if (answer == null) { + answer = defaultValue; + } + return answer; + } + + /** + * Looks up the given key in the given map, converting the result into + * a byte, using the default value if the the conversion fails. + * + * @param the key type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null or if the + * conversion fails + * @return the value in the map as a number, or defaultValue if the + * original value is null, the map is null or the number conversion fails + */ + public static Byte getByte(final Map map, final K key, final Byte defaultValue) { + Byte answer = getByte(map, key); + if (answer == null) { + answer = defaultValue; + } + return answer; + } + + /** + * Looks up the given key in the given map, converting the result into + * a short, using the default value if the the conversion fails. + * + * @param the key type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null or if the + * conversion fails + * @return the value in the map as a number, or defaultValue if the + * original value is null, the map is null or the number conversion fails + */ + public static Short getShort(final Map map, final K key, final Short defaultValue) { + Short answer = getShort(map, key); + if (answer == null) { + answer = defaultValue; + } + return answer; + } + + /** + * Looks up the given key in the given map, converting the result into + * an integer, using the default value if the the conversion fails. + * + * @param the key type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null or if the + * conversion fails + * @return the value in the map as a number, or defaultValue if the + * original value is null, the map is null or the number conversion fails + */ + public static Integer getInteger(final Map map, final K key, final Integer defaultValue) { + Integer answer = getInteger(map, key); + if (answer == null) { + answer = defaultValue; + } + return answer; + } + + /** + * Looks up the given key in the given map, converting the result into + * a long, using the default value if the the conversion fails. + * + * @param the key type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null or if the + * conversion fails + * @return the value in the map as a number, or defaultValue if the + * original value is null, the map is null or the number conversion fails + */ + public static Long getLong(final Map map, final K key, final Long defaultValue) { + Long answer = getLong(map, key); + if (answer == null) { + answer = defaultValue; + } + return answer; + } + + /** + * Looks up the given key in the given map, converting the result into + * a float, using the default value if the the conversion fails. + * + * @param the key type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null or if the + * conversion fails + * @return the value in the map as a number, or defaultValue if the + * original value is null, the map is null or the number conversion fails + */ + public static Float getFloat(final Map map, final K key, final Float defaultValue) { + Float answer = getFloat(map, key); + if (answer == null) { + answer = defaultValue; + } + return answer; + } + + /** + * Looks up the given key in the given map, converting the result into + * a double, using the default value if the the conversion fails. + * + * @param the key type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null or if the + * conversion fails + * @return the value in the map as a number, or defaultValue if the + * original value is null, the map is null or the number conversion fails + */ + public static Double getDouble(final Map map, final K key, final Double defaultValue) { + Double answer = getDouble(map, key); + if (answer == null) { + answer = defaultValue; + } + return answer; + } + + /** + * Looks up the given key in the given map, converting the result into + * a map, using the default value if the the conversion fails. + * + * @param the key type + * @param map the map whose value to look up + * @param key the key of the value to look up in that map + * @param defaultValue what to return if the value is null or if the + * conversion fails + * @return the value in the map as a number, or defaultValue if the + * original value is null, the map is null or the map conversion fails + */ + public static Map getMap(final Map map, final K key, final Map defaultValue) { + Map answer = getMap(map, key); + if (answer == null) { + answer = defaultValue; + } + return answer; + } + + // Type safe primitive getters + //------------------------------------------------------------------------- + /** + * Gets a boolean from a Map in a null-safe manner. + *

      + * If the value is a Boolean its value is returned. + * If the value is a String and it equals 'true' ignoring case + * then true is returned, otherwise false. + * If the value is a Number an integer zero value returns + * false and non-zero returns true. + * Otherwise, false is returned. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a Boolean, false if null map input + */ + public static boolean getBooleanValue(final Map map, final K key) { + return Boolean.TRUE.equals(getBoolean(map, key)); + } + + /** + * Gets a byte from a Map in a null-safe manner. + *

      + * The byte is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a byte, 0 if null map input + */ + public static byte getByteValue(final Map map, final K key) { + final Byte byteObject = getByte(map, key); + if (byteObject == null) { + return 0; + } + return byteObject.byteValue(); + } + + /** + * Gets a short from a Map in a null-safe manner. + *

      + * The short is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a short, 0 if null map input + */ + public static short getShortValue(final Map map, final K key) { + final Short shortObject = getShort(map, key); + if (shortObject == null) { + return 0; + } + return shortObject.shortValue(); + } + + /** + * Gets an int from a Map in a null-safe manner. + *

      + * The int is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as an int, 0 if null map input + */ + public static int getIntValue(final Map map, final K key) { + final Integer integerObject = getInteger(map, key); + if (integerObject == null) { + return 0; + } + return integerObject.intValue(); + } + + /** + * Gets a long from a Map in a null-safe manner. + *

      + * The long is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a long, 0L if null map input + */ + public static long getLongValue(final Map map, final K key) { + final Long longObject = getLong(map, key); + if (longObject == null) { + return 0L; + } + return longObject.longValue(); + } + + /** + * Gets a float from a Map in a null-safe manner. + *

      + * The float is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a float, 0.0F if null map input + */ + public static float getFloatValue(final Map map, final K key) { + final Float floatObject = getFloat(map, key); + if (floatObject == null) { + return 0f; + } + return floatObject.floatValue(); + } + + /** + * Gets a double from a Map in a null-safe manner. + *

      + * The double is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @return the value in the Map as a double, 0.0 if null map input + */ + public static double getDoubleValue(final Map map, final K key) { + final Double doubleObject = getDouble(map, key); + if (doubleObject == null) { + return 0d; + } + return doubleObject.doubleValue(); + } + + // Type safe primitive getters with default values + //------------------------------------------------------------------------- + /** + * Gets a boolean from a Map in a null-safe manner, + * using the default value if the the conversion fails. + *

      + * If the value is a Boolean its value is returned. + * If the value is a String and it equals 'true' ignoring case + * then true is returned, otherwise false. + * If the value is a Number an integer zero value returns + * false and non-zero returns true. + * Otherwise, defaultValue is returned. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @param defaultValue return if the value is null or if the conversion fails + * @return the value in the Map as a Boolean, defaultValue if null map input + */ + public static boolean getBooleanValue(final Map map, final K key, final boolean defaultValue) { + final Boolean booleanObject = getBoolean(map, key); + if (booleanObject == null) { + return defaultValue; + } + return booleanObject.booleanValue(); + } + + /** + * Gets a byte from a Map in a null-safe manner, + * using the default value if the the conversion fails. + *

      + * The byte is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @param defaultValue return if the value is null or if the conversion fails + * @return the value in the Map as a byte, defaultValue if null map input + */ + public static byte getByteValue(final Map map, final K key, final byte defaultValue) { + final Byte byteObject = getByte(map, key); + if (byteObject == null) { + return defaultValue; + } + return byteObject.byteValue(); + } + + /** + * Gets a short from a Map in a null-safe manner, + * using the default value if the the conversion fails. + *

      + * The short is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @param defaultValue return if the value is null or if the conversion fails + * @return the value in the Map as a short, defaultValue if null map input + */ + public static short getShortValue(final Map map, final K key, final short defaultValue) { + final Short shortObject = getShort(map, key); + if (shortObject == null) { + return defaultValue; + } + return shortObject.shortValue(); + } + + /** + * Gets an int from a Map in a null-safe manner, + * using the default value if the the conversion fails. + *

      + * The int is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @param defaultValue return if the value is null or if the conversion fails + * @return the value in the Map as an int, defaultValue if null map input + */ + public static int getIntValue(final Map map, final K key, final int defaultValue) { + final Integer integerObject = getInteger(map, key); + if (integerObject == null) { + return defaultValue; + } + return integerObject.intValue(); + } + + /** + * Gets a long from a Map in a null-safe manner, + * using the default value if the the conversion fails. + *

      + * The long is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @param defaultValue return if the value is null or if the conversion fails + * @return the value in the Map as a long, defaultValue if null map input + */ + public static long getLongValue(final Map map, final K key, final long defaultValue) { + final Long longObject = getLong(map, key); + if (longObject == null) { + return defaultValue; + } + return longObject.longValue(); + } + + /** + * Gets a float from a Map in a null-safe manner, + * using the default value if the the conversion fails. + *

      + * The float is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @param defaultValue return if the value is null or if the conversion fails + * @return the value in the Map as a float, defaultValue if null map input + */ + public static float getFloatValue(final Map map, final K key, final float defaultValue) { + final Float floatObject = getFloat(map, key); + if (floatObject == null) { + return defaultValue; + } + return floatObject.floatValue(); + } + + /** + * Gets a double from a Map in a null-safe manner, + * using the default value if the the conversion fails. + *

      + * The double is obtained from the results of {@link #getNumber(Map,Object)}. + * + * @param the key type + * @param map the map to use + * @param key the key to look up + * @param defaultValue return if the value is null or if the conversion fails + * @return the value in the Map as a double, defaultValue if null map input + */ + public static double getDoubleValue(final Map map, final K key, final double defaultValue) { + final Double doubleObject = getDouble(map, key); + if (doubleObject == null) { + return defaultValue; + } + return doubleObject.doubleValue(); + } + + // Conversion methods + //------------------------------------------------------------------------- + /** + * Gets a new Properties object initialised with the values from a Map. + * A null input will return an empty properties object. + *

      + * A Properties object may only store non-null keys and values, thus if + * the provided map contains either a key or value which is {@code null}, + * a {@link NullPointerException} will be thrown. + * + * @param the key type + * @param the value type + * @param map the map to convert to a Properties object + * @return the properties object + * @throws NullPointerException if a key or value in the provided map is {@code null} + */ + public static Properties toProperties(final Map map) { + final Properties answer = new Properties(); + if (map != null) { + for (final Entry entry2 : map.entrySet()) { + final Map.Entry entry = entry2; + final Object key = entry.getKey(); + final Object value = entry.getValue(); + answer.put(key, value); + } + } + return answer; + } + + /** + * Creates a new HashMap using data copied from a ResourceBundle. + * + * @param resourceBundle the resource bundle to convert, may not be null + * @return the hashmap containing the data + * @throws NullPointerException if the bundle is null + */ + public static Map toMap(final ResourceBundle resourceBundle) { + final Enumeration enumeration = resourceBundle.getKeys(); + final Map map = new HashMap(); + + while (enumeration.hasMoreElements()) { + final String key = enumeration.nextElement(); + final Object value = resourceBundle.getObject(key); + map.put(key, value); + } + + return map; + } + + // Printing methods + //------------------------------------------------------------------------- + /** + * Prints the given map with nice line breaks. + *

      + * This method prints a nicely formatted String describing the Map. + * Each map entry will be printed with key and value. + * When the value is a Map, recursive behaviour occurs. + *

      + * This method is NOT thread-safe in any special way. You must manually + * synchronize on either this class or the stream as required. + * + * @param out the stream to print to, must not be null + * @param label The label to be used, may be null. + * If null, the label is not output. + * It typically represents the name of the property in a bean or similar. + * @param map The map to print, may be null. + * If null, the text 'null' is output. + * @throws NullPointerException if the stream is null + */ + public static void verbosePrint(final PrintStream out, final Object label, final Map map) { + verbosePrintInternal(out, label, map, new ArrayDeque>(), false); + } + + /** + * Prints the given map with nice line breaks. + *

      + * This method prints a nicely formatted String describing the Map. + * Each map entry will be printed with key, value and value classname. + * When the value is a Map, recursive behaviour occurs. + *

      + * This method is NOT thread-safe in any special way. You must manually + * synchronize on either this class or the stream as required. + * + * @param out the stream to print to, must not be null + * @param label The label to be used, may be null. + * If null, the label is not output. + * It typically represents the name of the property in a bean or similar. + * @param map The map to print, may be null. + * If null, the text 'null' is output. + * @throws NullPointerException if the stream is null + */ + public static void debugPrint(final PrintStream out, final Object label, final Map map) { + verbosePrintInternal(out, label, map, new ArrayDeque>(), true); + } + + // Implementation methods + //------------------------------------------------------------------------- + /** + * Implementation providing functionality for {@link #debugPrint} and for + * {@link #verbosePrint}. This prints the given map with nice line breaks. + * If the debug flag is true, it additionally prints the type of the object + * value. If the contents of a map include the map itself, then the text + * (this Map) is printed out. If the contents include a + * parent container of the map, the the text (ancestor[i] Map) is + * printed, where i actually indicates the number of levels which must be + * traversed in the sequential list of ancestors (e.g. father, grandfather, + * great-grandfather, etc). + * + * @param out the stream to print to + * @param label the label to be used, may be null. + * If null, the label is not output. + * It typically represents the name of the property in a bean or similar. + * @param map the map to print, may be null. + * If null, the text 'null' is output + * @param lineage a stack consisting of any maps in which the previous + * argument is contained. This is checked to avoid infinite recursion when + * printing the output + * @param debug flag indicating whether type names should be output. + * @throws NullPointerException if the stream is null + */ + private static void verbosePrintInternal(final PrintStream out, final Object label, final Map map, + final Deque> lineage, final boolean debug) { + printIndent(out, lineage.size()); + + if (map == null) { + if (label != null) { + out.print(label); + out.print(" = "); + } + out.println("null"); + return; + } + if (label != null) { + out.print(label); + out.println(" = "); + } + + printIndent(out, lineage.size()); + out.println("{"); + + lineage.addLast(map); + + for (final Map.Entry entry : map.entrySet()) { + final Object childKey = entry.getKey(); + final Object childValue = entry.getValue(); + if (childValue instanceof Map && !lineage.contains(childValue)) { + verbosePrintInternal( + out, + childKey == null ? "null" : childKey, + (Map) childValue, + lineage, + debug); + } else { + printIndent(out, lineage.size()); + out.print(childKey); + out.print(" = "); + + final int lineageIndex = + IterableUtils.indexOf(lineage, + PredicateUtils.equalPredicate(childValue)); + if (lineageIndex == -1) { + out.print(childValue); + } else if (lineage.size() - 1 == lineageIndex) { + out.print("(this Map)"); + } else { + out.print( + "(ancestor[" + + (lineage.size() - 1 - lineageIndex - 1) + + "] Map)"); + } + + if (debug && childValue != null) { + out.print(' '); + out.println(childValue.getClass().getName()); + } else { + out.println(); + } + } + } + + lineage.removeLast(); + + printIndent(out, lineage.size()); + out.println(debug ? "} " + map.getClass().getName() : "}"); + } + + /** + * Writes indentation to the given stream. + * + * @param out the stream to indent + */ + private static void printIndent(final PrintStream out, final int indent) { + for (int i = 0; i < indent; i++) { + out.print(INDENT_STRING); + } + } + + // Misc + //----------------------------------------------------------------------- + /** + * Inverts the supplied map returning a new HashMap such that the keys of + * the input are swapped with the values. + *

      + * This operation assumes that the inverse mapping is well defined. + * If the input map had multiple entries with the same value mapped to + * different keys, the returned map will map one of those keys to the + * value, but the exact key which will be mapped is undefined. + * + * @param the key type + * @param the value type + * @param map the map to invert, may not be null + * @return a new HashMap containing the inverted data + * @throws NullPointerException if the map is null + */ + public static Map invertMap(final Map map) { + final Map out = new HashMap(map.size()); + for (final Entry entry : map.entrySet()) { + out.put(entry.getValue(), entry.getKey()); + } + return out; + } + + //----------------------------------------------------------------------- + /** + * Protects against adding null values to a map. + *

      + * This method checks the value being added to the map, and if it is null + * it is replaced by an empty string. + *

      + * This could be useful if the map does not accept null values, or for + * receiving data from a source that may provide null or empty string + * which should be held in the same way in the map. + *

      + * Keys are not validated. + * Note that this method can be used to circumvent the map's + * value type at runtime. + * + * @param the key type + * @param map the map to add to, may not be null + * @param key the key + * @param value the value, null converted to "" + * @throws NullPointerException if the map is null + */ + public static void safeAddToMap(final Map map, final K key, final Object value) + throws NullPointerException { + map.put(key, value == null ? "" : value); + } + + //----------------------------------------------------------------------- + /** + * Puts all the keys and values from the specified array into the map. + *

      + * This method is an alternative to the {@link java.util.Map#putAll(java.util.Map)} + * method and constructors. It allows you to build a map from an object array + * of various possible styles. + *

      + * If the first entry in the object array implements {@link java.util.Map.Entry} + * or {@link KeyValue} then the key and value are added from that object. + * If the first entry in the object array is an object array itself, then + * it is assumed that index 0 in the sub-array is the key and index 1 is the value. + * Otherwise, the array is treated as keys and values in alternate indices. + *

      + * For example, to create a color map: + *

      +     * Map colorMap = MapUtils.putAll(new HashMap(), new String[][] {
      +     *     {"RED", "#FF0000"},
      +     *     {"GREEN", "#00FF00"},
      +     *     {"BLUE", "#0000FF"}
      +     * });
      +     * 
      + * or: + *
      +     * Map colorMap = MapUtils.putAll(new HashMap(), new String[] {
      +     *     "RED", "#FF0000",
      +     *     "GREEN", "#00FF00",
      +     *     "BLUE", "#0000FF"
      +     * });
      +     * 
      + * or: + *
      +     * Map colorMap = MapUtils.putAll(new HashMap(), new Map.Entry[] {
      +     *     new DefaultMapEntry("RED", "#FF0000"),
      +     *     new DefaultMapEntry("GREEN", "#00FF00"),
      +     *     new DefaultMapEntry("BLUE", "#0000FF")
      +     * });
      +     * 
      + * + * @param the key type + * @param the value type + * @param map the map to populate, must not be null + * @param array an array to populate from, null ignored + * @return the input map + * @throws NullPointerException if map is null + * @throws IllegalArgumentException if sub-array or entry matching used and an entry is invalid + * @throws ClassCastException if the array contents is mixed + * @since 3.2 + */ + @SuppressWarnings("unchecked") // As per Javadoc throws CCE for invalid array contents + public static Map putAll(final Map map, final Object[] array) { + if (map == null) { + throw new NullPointerException("The map must not be null"); + } + if (array == null || array.length == 0) { + return map; + } + final Object obj = array[0]; + if (obj instanceof Map.Entry) { + for (final Object element : array) { + // cast ok here, type is checked above + final Map.Entry entry = (Map.Entry) element; + map.put(entry.getKey(), entry.getValue()); + } + } else if (obj instanceof KeyValue) { + for (final Object element : array) { + // cast ok here, type is checked above + final KeyValue keyval = (KeyValue) element; + map.put(keyval.getKey(), keyval.getValue()); + } + } else if (obj instanceof Object[]) { + for (int i = 0; i < array.length; i++) { + final Object[] sub = (Object[]) array[i]; + if (sub == null || sub.length < 2) { + throw new IllegalArgumentException("Invalid array element: " + i); + } + // these casts can fail if array has incorrect types + map.put((K) sub[0], (V) sub[1]); + } + } else { + for (int i = 0; i < array.length - 1;) { + // these casts can fail if array has incorrect types + map.put((K) array[i++], (V) array[i++]); + } + } + return map; + } + + //----------------------------------------------------------------------- + + /** + * Returns an immutable empty map if the argument is null, + * or the argument itself otherwise. + * + * @param the key type + * @param the value type + * @param map the map, possibly null + * @return an empty map if the argument is null + */ + public static Map emptyIfNull(final Map map) { + return map == null ? Collections.emptyMap() : map; + } + + /** + * Null-safe check if the specified map is empty. + *

      + * Null returns true. + * + * @param map the map to check, may be null + * @return true if empty or null + * @since 3.2 + */ + public static boolean isEmpty(final Map map) { + return map == null || map.isEmpty(); + } + + /** + * Null-safe check if the specified map is not empty. + *

      + * Null returns false. + * + * @param map the map to check, may be null + * @return true if non-null and non-empty + * @since 3.2 + */ + public static boolean isNotEmpty(final Map map) { + return !MapUtils.isEmpty(map); + } + + // Map decorators + //----------------------------------------------------------------------- + /** + * Returns a synchronized map backed by the given map. + *

      + * You must manually synchronize on the returned buffer's iterator to + * avoid non-deterministic behavior: + * + *

      +     * Map m = MapUtils.synchronizedMap(myMap);
      +     * Set s = m.keySet();  // outside synchronized block
      +     * synchronized (m) {  // synchronized on MAP!
      +     *     Iterator i = s.iterator();
      +     *     while (i.hasNext()) {
      +     *         process (i.next());
      +     *     }
      +     * }
      +     * 
      + * + * This method uses the implementation in {@link java.util.Collections Collections}. + * + * @param the key type + * @param the value type + * @param map the map to synchronize, must not be null + * @return a synchronized map backed by the given map + */ + public static Map synchronizedMap(final Map map) { + return Collections.synchronizedMap(map); + } + + /** + * Returns an unmodifiable map backed by the given map. + *

      + * This method uses the implementation in the decorators subpackage. + * + * @param the key type + * @param the value type + * @param map the map to make unmodifiable, must not be null + * @return an unmodifiable map backed by the given map + * @throws NullPointerException if the map is null + */ + public static Map unmodifiableMap(final Map map) { + return UnmodifiableMap.unmodifiableMap(map); + } + + /** + * Returns a predicated (validating) map backed by the given map. + *

      + * Only objects that pass the tests in the given predicates can be added to the map. + * Trying to add an invalid object results in an IllegalArgumentException. + * Keys must pass the key predicate, values must pass the value predicate. + * It is important not to use the original map after invoking this method, + * as it is a backdoor for adding invalid objects. + * + * @param the key type + * @param the value type + * @param map the map to predicate, must not be null + * @param keyPred the predicate for keys, null means no check + * @param valuePred the predicate for values, null means no check + * @return a predicated map backed by the given map + * @throws NullPointerException if the Map is null + */ + public static IterableMap predicatedMap(final Map map, final Predicate keyPred, + final Predicate valuePred) { + return PredicatedMap.predicatedMap(map, keyPred, valuePred); + } + + /** + * Returns a transformed map backed by the given map. + *

      + * This method returns a new map (decorating the specified map) that + * will transform any new entries added to it. + * Existing entries in the specified map will not be transformed. + * If you want that behaviour, see {@link TransformedMap#transformedMap}. + *

      + * Each object is passed through the transformers as it is added to the + * Map. It is important not to use the original map after invoking this + * method, as it is a backdoor for adding untransformed objects. + *

      + * If there are any elements already in the map being decorated, they + * are NOT transformed. + * + * @param the key type + * @param the value type + * @param map the map to transform, must not be null, typically empty + * @param keyTransformer the transformer for the map keys, null means no transformation + * @param valueTransformer the transformer for the map values, null means no transformation + * @return a transformed map backed by the given map + * @throws NullPointerException if the Map is null + */ + public static IterableMap transformedMap(final Map map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + return TransformedMap.transformingMap(map, keyTransformer, valueTransformer); + } + + /** + * Returns a fixed-sized map backed by the given map. + * Elements may not be added or removed from the returned map, but + * existing elements can be changed (for instance, via the + * {@link Map#put(Object,Object)} method). + * + * @param the key type + * @param the value type + * @param map the map whose size to fix, must not be null + * @return a fixed-size map backed by that map + * @throws NullPointerException if the Map is null + */ + public static IterableMap fixedSizeMap(final Map map) { + return FixedSizeMap.fixedSizeMap(map); + } + + /** + * Returns a "lazy" map whose values will be created on demand. + *

      + * When the key passed to the returned map's {@link Map#get(Object)} + * method is not present in the map, then the factory will be used + * to create a new object and that object will become the value + * associated with that key. + *

      + * For instance: + *

      +     * Factory factory = new Factory() {
      +     *     public Object create() {
      +     *         return new Date();
      +     *     }
      +     * }
      +     * Map lazyMap = MapUtils.lazyMap(new HashMap(), factory);
      +     * Object obj = lazyMap.get("test");
      +     * 
      + * + * After the above code is executed, obj will contain + * a new Date instance. Furthermore, that Date + * instance is the value for the "test" key in the map. + * + * @param the key type + * @param the value type + * @param map the map to make lazy, must not be null + * @param factory the factory for creating new objects, must not be null + * @return a lazy map backed by the given map + * @throws NullPointerException if the Map or Factory is null + */ + public static IterableMap lazyMap(final Map map, final Factory factory) { + return LazyMap.lazyMap(map, factory); + } + + /** + * Returns a "lazy" map whose values will be created on demand. + *

      + * When the key passed to the returned map's {@link Map#get(Object)} + * method is not present in the map, then the factory will be used + * to create a new object and that object will become the value + * associated with that key. The factory is a {@link Transformer} + * that will be passed the key which it must transform into the value. + *

      + * For instance: + *

      +     * Transformer factory = new Transformer() {
      +     *     public Object transform(Object mapKey) {
      +     *         return new File(mapKey);
      +     *     }
      +     * }
      +     * Map lazyMap = MapUtils.lazyMap(new HashMap(), factory);
      +     * Object obj = lazyMap.get("C:/dev");
      +     * 
      + * + * After the above code is executed, obj will contain + * a new File instance for the C drive dev directory. + * Furthermore, that File instance is the value for the + * "C:/dev" key in the map. + *

      + * If a lazy map is wrapped by a synchronized map, the result is a simple + * synchronized cache. When an object is not is the cache, the cache itself + * calls back to the factory Transformer to populate itself, all within the + * same synchronized block. + * + * @param the key type + * @param the value type + * @param map the map to make lazy, must not be null + * @param transformerFactory the factory for creating new objects, must not be null + * @return a lazy map backed by the given map + * @throws NullPointerException if the Map or Transformer is null + */ + public static IterableMap lazyMap(final Map map, + final Transformer transformerFactory) { + return LazyMap.lazyMap(map, transformerFactory); + } + + /** + * Returns a map that maintains the order of keys that are added + * backed by the given map. + *

      + * If a key is added twice, the order is determined by the first add. + * The order is observed through the keySet, values and entrySet. + * + * @param the key type + * @param the value type + * @param map the map to order, must not be null + * @return an ordered map backed by the given map + * @throws NullPointerException if the Map is null + */ + public static OrderedMap orderedMap(final Map map) { + return ListOrderedMap.listOrderedMap(map); + } + + /** + * Creates a mult-value map backed by the given map which returns + * collections of type ArrayList. + * + * @param the key type + * @param the value type + * @param map the map to decorate + * @return a multi-value map backed by the given map which returns ArrayLists of values. + * @see MultiValueMap + * @since 3.2 + * @deprecated since 4.1, use {@link MultiValuedMap} instead + */ + @Deprecated + public static MultiValueMap multiValueMap(final Map> map) { + return MultiValueMap.multiValueMap(map); + } + + /** + * Creates a multi-value map backed by the given map which returns + * collections of the specified type. + * + * @param the key type + * @param the value type + * @param the collection class type + * @param map the map to decorate + * @param collectionClass the type of collections to return from the map + * (must contain public no-arg constructor and extend Collection) + * @return a multi-value map backed by the given map which returns collections of the specified type + * @see MultiValueMap + * @since 3.2 + * @deprecated since 4.1, use {@link MultiValuedMap} instead + */ + @Deprecated + public static > MultiValueMap multiValueMap(final Map map, + final Class collectionClass) { + return MultiValueMap.multiValueMap(map, collectionClass); + } + + /** + * Creates a multi-value map backed by the given map which returns + * collections created by the specified collection factory. + * + * @param the key type + * @param the value type + * @param the collection class type + * @param map the map to decorate + * @param collectionFactory a factor which creates collection objects + * @return a multi-value map backed by the given map which returns collections + * created by the specified collection factory + * @see MultiValueMap + * @since 3.2 + * @deprecated since 4.1, use {@link MultiValuedMap} instead + */ + @Deprecated + public static > MultiValueMap multiValueMap(final Map map, + final Factory collectionFactory) { + return MultiValueMap.multiValueMap(map, collectionFactory); + } + + // SortedMap decorators + //----------------------------------------------------------------------- + /** + * Returns a synchronized sorted map backed by the given sorted map. + *

      + * You must manually synchronize on the returned buffer's iterator to + * avoid non-deterministic behavior: + * + *

      +     * Map m = MapUtils.synchronizedSortedMap(myMap);
      +     * Set s = m.keySet();  // outside synchronized block
      +     * synchronized (m) {  // synchronized on MAP!
      +     *     Iterator i = s.iterator();
      +     *     while (i.hasNext()) {
      +     *         process (i.next());
      +     *     }
      +     * }
      +     * 
      + * + * This method uses the implementation in {@link java.util.Collections Collections}. + * + * @param the key type + * @param the value type + * @param map the map to synchronize, must not be null + * @return a synchronized map backed by the given map + * @throws NullPointerException if the map is null + */ + public static SortedMap synchronizedSortedMap(final SortedMap map) { + return Collections.synchronizedSortedMap(map); + } + + /** + * Returns an unmodifiable sorted map backed by the given sorted map. + *

      + * This method uses the implementation in the decorators subpackage. + * + * @param the key type + * @param the value type + * @param map the sorted map to make unmodifiable, must not be null + * @return an unmodifiable map backed by the given map + * @throws NullPointerException if the map is null + */ + public static SortedMap unmodifiableSortedMap(final SortedMap map) { + return UnmodifiableSortedMap.unmodifiableSortedMap(map); + } + + /** + * Returns a predicated (validating) sorted map backed by the given map. + *

      + * Only objects that pass the tests in the given predicates can be added to the map. + * Trying to add an invalid object results in an IllegalArgumentException. + * Keys must pass the key predicate, values must pass the value predicate. + * It is important not to use the original map after invoking this method, + * as it is a backdoor for adding invalid objects. + * + * @param the key type + * @param the value type + * @param map the map to predicate, must not be null + * @param keyPred the predicate for keys, null means no check + * @param valuePred the predicate for values, null means no check + * @return a predicated map backed by the given map + * @throws NullPointerException if the SortedMap is null + */ + public static SortedMap predicatedSortedMap(final SortedMap map, + final Predicate keyPred, final Predicate valuePred) { + return PredicatedSortedMap.predicatedSortedMap(map, keyPred, valuePred); + } + + /** + * Returns a transformed sorted map backed by the given map. + *

      + * This method returns a new sorted map (decorating the specified map) that + * will transform any new entries added to it. + * Existing entries in the specified map will not be transformed. + * If you want that behaviour, see {@link TransformedSortedMap#transformedSortedMap}. + *

      + * Each object is passed through the transformers as it is added to the + * Map. It is important not to use the original map after invoking this + * method, as it is a backdoor for adding untransformed objects. + *

      + * If there are any elements already in the map being decorated, they + * are NOT transformed. + * + * @param the key type + * @param the value type + * @param map the map to transform, must not be null, typically empty + * @param keyTransformer the transformer for the map keys, null means no transformation + * @param valueTransformer the transformer for the map values, null means no transformation + * @return a transformed map backed by the given map + * @throws NullPointerException if the SortedMap is null + */ + public static SortedMap transformedSortedMap(final SortedMap map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + return TransformedSortedMap.transformingSortedMap(map, keyTransformer, valueTransformer); + } + + /** + * Returns a fixed-sized sorted map backed by the given sorted map. + * Elements may not be added or removed from the returned map, but + * existing elements can be changed (for instance, via the + * {@link Map#put(Object,Object)} method). + * + * @param the key type + * @param the value type + * @param map the map whose size to fix, must not be null + * @return a fixed-size map backed by that map + * @throws NullPointerException if the SortedMap is null + */ + public static SortedMap fixedSizeSortedMap(final SortedMap map) { + return FixedSizeSortedMap.fixedSizeSortedMap(map); + } + + /** + * Returns a "lazy" sorted map whose values will be created on demand. + *

      + * When the key passed to the returned map's {@link Map#get(Object)} + * method is not present in the map, then the factory will be used + * to create a new object and that object will become the value + * associated with that key. + *

      + * For instance: + * + *

      +     * Factory factory = new Factory() {
      +     *     public Object create() {
      +     *         return new Date();
      +     *     }
      +     * }
      +     * SortedMap lazy = MapUtils.lazySortedMap(new TreeMap(), factory);
      +     * Object obj = lazy.get("test");
      +     * 
      + * + * After the above code is executed, obj will contain + * a new Date instance. Furthermore, that Date + * instance is the value for the "test" key. + * + * @param the key type + * @param the value type + * @param map the map to make lazy, must not be null + * @param factory the factory for creating new objects, must not be null + * @return a lazy map backed by the given map + * @throws NullPointerException if the SortedMap or Factory is null + */ + public static SortedMap lazySortedMap(final SortedMap map, final Factory factory) { + return LazySortedMap.lazySortedMap(map, factory); + } + + /** + * Returns a "lazy" sorted map whose values will be created on demand. + *

      + * When the key passed to the returned map's {@link Map#get(Object)} + * method is not present in the map, then the factory will be used + * to create a new object and that object will become the value + * associated with that key. The factory is a {@link Transformer} + * that will be passed the key which it must transform into the value. + *

      + * For instance: + *

      +     * Transformer factory = new Transformer() {
      +     *     public Object transform(Object mapKey) {
      +     *         return new File(mapKey);
      +     *     }
      +     * }
      +     * SortedMap lazy = MapUtils.lazySortedMap(new TreeMap(), factory);
      +     * Object obj = lazy.get("C:/dev");
      +     * 
      + * + * After the above code is executed, obj will contain + * a new File instance for the C drive dev directory. + * Furthermore, that File instance is the value for the + * "C:/dev" key in the map. + *

      + * If a lazy map is wrapped by a synchronized map, the result is a simple + * synchronized cache. When an object is not is the cache, the cache itself + * calls back to the factory Transformer to populate itself, all within the + * same synchronized block. + * + * @param the key type + * @param the value type + * @param map the map to make lazy, must not be null + * @param transformerFactory the factory for creating new objects, must not be null + * @return a lazy map backed by the given map + * @throws NullPointerException if the Map or Transformer is null + */ + public static SortedMap lazySortedMap(final SortedMap map, + final Transformer transformerFactory) { + return LazySortedMap.lazySortedMap(map, transformerFactory); + } + + /** + * Populates a Map using the supplied Transformer to transform the elements + * into keys, using the unaltered element as the value in the Map. + * + * @param the key type + * @param the value type + * @param map the Map to populate. + * @param elements the Iterable containing the input values for the map. + * @param keyTransformer the Transformer used to transform the element into a key value + * @throws NullPointerException if the map, elements or transformer are null + */ + public static void populateMap(final Map map, final Iterable elements, + final Transformer keyTransformer) { + populateMap(map, elements, keyTransformer, TransformerUtils.nopTransformer()); + } + + /** + * Populates a Map using the supplied Transformers to transform the elements + * into keys and values. + * + * @param the key type + * @param the value type + * @param the type of object contained in the {@link Iterable} + * @param map the Map to populate. + * @param elements the Iterable containing the input values for the map. + * @param keyTransformer the Transformer used to transform the element into a key value + * @param valueTransformer the Transformer used to transform the element into a value + * @throws NullPointerException if the map, elements or transformers are null + */ + public static void populateMap(final Map map, final Iterable elements, + final Transformer keyTransformer, + final Transformer valueTransformer) { + final Iterator iter = elements.iterator(); + while (iter.hasNext()) { + final E temp = iter.next(); + map.put(keyTransformer.transform(temp), valueTransformer.transform(temp)); + } + } + + /** + * Populates a MultiMap using the supplied Transformer to transform the elements + * into keys, using the unaltered element as the value in the MultiMap. + * + * @param the key type + * @param the value type + * @param map the MultiMap to populate. + * @param elements the Iterable to use as input values for the map. + * @param keyTransformer the Transformer used to transform the element into a key value + * @throws NullPointerException if the map, elements or transformer are null + */ + public static void populateMap(final MultiMap map, final Iterable elements, + final Transformer keyTransformer) { + populateMap(map, elements, keyTransformer, TransformerUtils.nopTransformer()); + } + + /** + * Populates a MultiMap using the supplied Transformers to transform the elements + * into keys and values. + * + * @param the key type + * @param the value type + * @param the type of object contained in the {@link Iterable} + * @param map the MultiMap to populate. + * @param elements the Iterable containing the input values for the map. + * @param keyTransformer the Transformer used to transform the element into a key value + * @param valueTransformer the Transformer used to transform the element into a value + * @throws NullPointerException if the map, collection or transformers are null + */ + public static void populateMap(final MultiMap map, final Iterable elements, + final Transformer keyTransformer, + final Transformer valueTransformer) { + final Iterator iter = elements.iterator(); + while (iter.hasNext()) { + final E temp = iter.next(); + map.put(keyTransformer.transform(temp), valueTransformer.transform(temp)); + } + } + + /** + * Get the specified {@link Map} as an {@link IterableMap}. + * + * @param the key type + * @param the value type + * @param map to wrap if necessary. + * @return IterableMap + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static IterableMap iterableMap(final Map map) { + if (map == null) { + throw new NullPointerException("Map must not be null"); + } + return map instanceof IterableMap ? (IterableMap) map : new AbstractMapDecorator(map) {}; + } + + /** + * Get the specified {@link SortedMap} as an {@link IterableSortedMap}. + * + * @param the key type + * @param the value type + * @param sortedMap to wrap if necessary + * @return {@link IterableSortedMap} + * @throws NullPointerException if sortedMap is null + * @since 4.0 + */ + public static IterableSortedMap iterableSortedMap(final SortedMap sortedMap) { + if (sortedMap == null) { + throw new NullPointerException("Map must not be null"); + } + return sortedMap instanceof IterableSortedMap ? (IterableSortedMap) sortedMap : + new AbstractSortedMapDecorator(sortedMap) {}; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/MultiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/MultiMap.java new file mode 100644 index 000000000..079a63690 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/MultiMap.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; + +/** + * Defines a map that holds a collection of values against each key. + *

      + * A MultiMap is a Map with slightly different semantics. + * Putting a value into the map will add the value to a Collection at that key. + * Getting a value will return a Collection, holding all the values put to that key. + *

      + * For example: + *

      + * MultiMap mhm = new MultiValueMap();
      + * mhm.put(key, "A");
      + * mhm.put(key, "B");
      + * mhm.put(key, "C");
      + * Collection coll = (Collection) mhm.get(key);
      + *

      + * coll will be a collection containing "A", "B", "C". + *

      + * NOTE: Additional methods were added to this interface in Commons Collections 3.1. + * These were added solely for documentation purposes and do not change the interface + * as they were defined in the superinterface Map anyway. + * + * @since 2.0 + * @version $Id: MultiMap.java 1683018 2015-06-01 22:41:31Z tn $ + * @deprecated since 4.1, use {@link MultiValuedMap} instead + */ +@Deprecated +public interface MultiMap extends IterableMap { + + /** + * Removes a specific value from map. + *

      + * The item is removed from the collection mapped to the specified key. + * Other values attached to that key are unaffected. + *

      + * If the last value for a key is removed, implementations typically + * return null from a subsequent get(Object), however + * they may choose to return an empty collection. + * + * @param key the key to remove from + * @param item the item to remove + * @return {@code true} if the mapping was removed, {@code false} otherwise + * @throws UnsupportedOperationException if the map is unmodifiable + * @throws ClassCastException if the key or value is of an invalid type + * @throws NullPointerException if the key or value is null and null is invalid + * @since 4.0 (signature in previous releases: V remove(K, V)) + */ + boolean removeMapping(K key, V item); + + //----------------------------------------------------------------------- + /** + * Gets the number of keys in this map. + *

      + * Implementations typically return only the count of keys in the map + * This cannot be mandated due to backwards compatibility of this interface. + * + * @return the number of key-collection mappings in this map + */ + int size(); + + /** + * Gets the collection of values associated with the specified key. + *

      + * The returned value will implement Collection. Implementations + * are free to declare that they return Collection subclasses + * such as List or Set. + *

      + * Implementations typically return null if no values have + * been mapped to the key, however the implementation may choose to + * return an empty collection. + *

      + * Implementations may choose to return a clone of the internal collection. + * + * @param key the key to retrieve + * @return the Collection of values, implementations should + * return null for no mapping, but may return an empty collection + * @throws ClassCastException if the key is of an invalid type + * @throws NullPointerException if the key is null and null keys are invalid + */ + Object get(Object key); // Cannot use get(K key) as that does not properly implement Map#get + + /** + * Checks whether the map contains the value specified. + *

      + * Implementations typically check all collections against all keys for the value. + * This cannot be mandated due to backwards compatibility of this interface. + * + * @param value the value to search for + * @return true if the map contains the value + * @throws ClassCastException if the value is of an invalid type + * @throws NullPointerException if the value is null and null value are invalid + */ + boolean containsValue(Object value); + + /** + * Adds the value to the collection associated with the specified key. + *

      + * Unlike a normal Map the previous value is not replaced. + * Instead the new value is added to the collection stored against the key. + * The collection may be a List, Set or other + * collection dependent on implementation. + * + * @param key the key to store against + * @param value the value to add to the collection at the key + * @return typically the value added if the map changed and null if the map did not change + * @throws UnsupportedOperationException if the map is unmodifiable + * @throws ClassCastException if the key or value is of an invalid type + * @throws NullPointerException if the key or value is null and null is invalid + * @throws IllegalArgumentException if the key or value is invalid + */ + Object put(K key, Object value); + + /** + * Removes all values associated with the specified key. + *

      + * Implementations typically return null from a subsequent + * get(Object), however they may choose to return an empty collection. + * + * @param key the key to remove values from + * @return the Collection of values removed, implementations should + * return null for no mapping found, but may return an empty collection + * @throws UnsupportedOperationException if the map is unmodifiable + * @throws ClassCastException if the key is of an invalid type + * @throws NullPointerException if the key is null and null keys are invalid + */ + Object remove(Object key); // Cannot use remove(K key) as that does not properly implement Map#remove + + /** + * Gets a collection containing all the values in the map. + *

      + * Implementations typically return a collection containing the combination + * of values from all keys. + * This cannot be mandated due to backwards compatibility of this interface. + * + * @return a collection view of the values contained in this map + */ + Collection values(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/MultiMapUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/MultiMapUtils.java new file mode 100644 index 000000000..95d9b8616 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/MultiMapUtils.java @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.collections4.bag.HashBag; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; +import org.apache.commons.collections4.multimap.HashSetValuedHashMap; +import org.apache.commons.collections4.multimap.TransformedMultiValuedMap; +import org.apache.commons.collections4.multimap.UnmodifiableMultiValuedMap; + +/** + * Provides utility methods and decorators for {@link MultiValuedMap} instances. + *

      + * It contains various type safe and null safe methods. Additionally, it provides + * the following decorators: + *

        + *
      • {@link #unmodifiableMultiValuedMap(MultiValuedMap)}
      • + *
      • {@link #transformedMultiValuedMap(MultiValuedMap, Transformer, Transformer)}
      • + *
      + * + * @since 4.1 + * @version $Id: MultiMapUtils.java 1715302 2015-11-19 23:08:01Z tn $ + */ +public class MultiMapUtils { + + /** + * MultiMapUtils should not normally be instantiated. + */ + private MultiMapUtils() {} + + /** + * An empty {@link UnmodifiableMultiValuedMap}. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static final MultiValuedMap EMPTY_MULTI_VALUED_MAP = + UnmodifiableMultiValuedMap.unmodifiableMultiValuedMap(new ArrayListValuedHashMap(0, 0)); + + /** + * Returns immutable EMPTY_MULTI_VALUED_MAP with generic type safety. + * + * @param the type of key in the map + * @param the type of value in the map + * @return immutable and empty MultiValuedMap + */ + @SuppressWarnings("unchecked") + public static MultiValuedMap emptyMultiValuedMap() { + return EMPTY_MULTI_VALUED_MAP; + } + + // Null safe methods + + /** + * Returns an immutable empty MultiValuedMap if the argument is + * null, or the argument itself otherwise. + * + * @param the type of key in the map + * @param the type of value in the map + * @param map the map, may be null + * @return an empty {@link MultiValuedMap} if the argument is null + */ + @SuppressWarnings("unchecked") + public static MultiValuedMap emptyIfNull(final MultiValuedMap map) { + return map == null ? EMPTY_MULTI_VALUED_MAP : map; + } + + /** + * Null-safe check if the specified MultiValuedMap is empty. + *

      + * If the provided map is null, returns true. + * + * @param map the map to check, may be null + * @return true if the map is empty or null + */ + public static boolean isEmpty(final MultiValuedMap map) { + return map == null || map.isEmpty(); + } + + // Null safe getters + // ------------------------------------------------------------------------- + + /** + * Gets a Collection from MultiValuedMap in a null-safe manner. + * + * @param the key type + * @param the value type + * @param map the {@link MultiValuedMap} to use + * @param key the key to look up + * @return the Collection in the {@link MultiValuedMap}, or null if input map is null + */ + public static Collection getCollection(final MultiValuedMap map, final K key) { + if (map != null) { + return map.get(key); + } + return null; + } + + // TODO: review the getValuesAsXXX methods - depending on the actual MultiValuedMap type, changes + // to the returned collection might update the backing map. This should be clarified and/or prevented. + + /** + * Gets a List from MultiValuedMap in a null-safe manner. + * + * @param the key type + * @param the value type + * @param map the {@link MultiValuedMap} to use + * @param key the key to look up + * @return the Collection in the {@link MultiValuedMap} as List, or null if input map is null + */ + public static List getValuesAsList(final MultiValuedMap map, final K key) { + if (map != null) { + Collection col = map.get(key); + if (col instanceof List) { + return (List) col; + } + return new ArrayList(col); + } + return null; + } + + /** + * Gets a Set from MultiValuedMap in a null-safe manner. + * + * @param the key type + * @param the value type + * @param map the {@link MultiValuedMap} to use + * @param key the key to look up + * @return the Collection in the {@link MultiValuedMap} as Set, or null if input map is null + */ + public static Set getValuesAsSet(final MultiValuedMap map, final K key) { + if (map != null) { + Collection col = map.get(key); + if (col instanceof Set) { + return (Set) col; + } + return new HashSet(col); + } + return null; + } + + /** + * Gets a Bag from MultiValuedMap in a null-safe manner. + * + * @param the key type + * @param the value type + * @param map the {@link MultiValuedMap} to use + * @param key the key to look up + * @return the Collection in the {@link MultiValuedMap} as Bag, or null if input map is null + */ + public static Bag getValuesAsBag(final MultiValuedMap map, final K key) { + if (map != null) { + Collection col = map.get(key); + if (col instanceof Bag) { + return (Bag) col; + } + return new HashBag(col); + } + return null; + } + + // Factory Methods + // ----------------------------------------------------------------------- + + /** + * Creates a {@link ListValuedMap} with an {@link java.util.ArrayList ArrayList} as + * collection class to store the values mapped to a key. + * + * @param the key type + * @param the value type + * @return a new ListValuedMap + */ + public static ListValuedMap newListValuedHashMap() { + return new ArrayListValuedHashMap(); + } + + /** + * Creates a {@link SetValuedMap} with an {@link java.util.HashSet HashSet} as + * collection class to store the values mapped to a key. + * + * @param the key type + * @param the value type + * @return a new {@link SetValuedMap} + */ + public static SetValuedMap newSetValuedHashMap() { + return new HashSetValuedHashMap(); + } + + // MultiValuedMap Decorators + // ----------------------------------------------------------------------- + + /** + * Returns an UnmodifiableMultiValuedMap backed by the given + * map. + * + * @param the key type + * @param the value type + * @param map the {@link MultiValuedMap} to decorate, must not be null + * @return an unmodifiable {@link MultiValuedMap} backed by the provided map + * @throws NullPointerException if map is null + */ + public static MultiValuedMap unmodifiableMultiValuedMap( + final MultiValuedMap map) { + return UnmodifiableMultiValuedMap.unmodifiableMultiValuedMap(map); + } + + /** + * Returns a TransformedMultiValuedMap backed by the given map. + *

      + * This method returns a new MultiValuedMap (decorating the + * specified map) that will transform any new entries added to it. Existing + * entries in the specified map will not be transformed. If you want that + * behaviour, see {@link TransformedMultiValuedMap#transformedMap}. + *

      + * Each object is passed through the transformers as it is added to the Map. + * It is important not to use the original map after invoking this method, + * as it is a back door for adding untransformed objects. + *

      + * If there are any elements already in the map being decorated, they are + * NOT transformed. + * + * @param the key type + * @param the value type + * @param map the {@link MultiValuedMap} to transform, must not be null, typically empty + * @param keyTransformer the transformer for the map keys, null means no transformation + * @param valueTransformer the transformer for the map values, null means no transformation + * @return a transformed MultiValuedMap backed by the given map + * @throws NullPointerException if map is null + */ + public static MultiValuedMap transformedMultiValuedMap(final MultiValuedMap map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + return TransformedMultiValuedMap.transformingMap(map, keyTransformer, valueTransformer); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/MultiSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/MultiSet.java new file mode 100644 index 000000000..fd51b6320 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/MultiSet.java @@ -0,0 +1,272 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +/** + * Defines a collection that counts the number of times an object appears in + * the collection. + *

      + * Suppose you have a MultiSet that contains {a, a, b, c}. + * Calling {@link #getCount(Object)} on a would return 2, while + * calling {@link #uniqueSet()} would return {a, b, c}. + * + * @param the type held in the multiset + * @since 4.1 + * @version $Id: MultiSet.java 1714424 2015-11-15 10:06:16Z tn $ + */ +public interface MultiSet extends Collection { + + /** + * Returns the number of occurrences of the given object currently + * in the MultiSet. If the object does not exist in the multiset, + * return 0. + * + * @param object the object to search for + * @return the number of occurrences of the object, zero if not found + */ + int getCount(Object object); + + /** + * Sets the number of occurrences of the specified object in the MultiSet + * to the given count. + *

      + * If the provided count is zero, the object will be removed from the + * {@link #uniqueSet()}. + * + * @param object the object to update + * @param count the number of occurrences of the object + * @return the number of occurrences of the object before this operation, zero + * if the object was not contained in the multiset + * @throws IllegalArgumentException if count is negative + */ + int setCount(E object, int count); + + /** + * Adds one copy of the specified object to the MultiSet. + *

      + * If the object is already in the {@link #uniqueSet()} then increment its + * count as reported by {@link #getCount(Object)}. Otherwise add it to the + * {@link #uniqueSet()} and report its count as 1. + * + * @param object the object to add + * @return true always, as the size of the MultiSet is increased + * in any case + */ + @Override + boolean add(E object); + + /** + * Adds a number of occurrences of the specified object to the MultiSet. + *

      + * If the object is already in the {@link #uniqueSet()} then increment its + * count as reported by {@link #getCount(Object)}. Otherwise add it to the + * {@link #uniqueSet()} and report its count as occurrences. + * + * @param object the object to add + * @param occurrences the number of occurrences to add, may be zero, + * in which case no change is made to the multiset + * @return the number of occurrences of the object in the multiset before + * this operation; possibly zero + * @throws IllegalArgumentException if occurrences is negative + */ + int add(E object, int occurrences); + + /** + * Removes one occurrence of the given object from the MultiSet. + *

      + * If the number of occurrences after this operations is reduced + * to zero, the object will be removed from the {@link #uniqueSet()}. + * + * @param object the object to remove + * @return true if this call changed the collection + */ + @Override + boolean remove(Object object); + + /** + * Removes a number of occurrences of the specified object from the MultiSet. + *

      + * If the number of occurrences to remove is greater than the actual number of + * occurrences in the multiset, the object will be removed from the multiset. + * + * @param object the object to remove + * @param occurrences the number of occurrences to remove, may be zero, + * in which case no change is made to the multiset + * @return the number of occurrences of the object in the multiset + * before the operation; possibly zero + * @throws IllegalArgumentException if occurrences is negative + */ + int remove(Object object, int occurrences); + + /** + * Returns a {@link Set} of unique elements in the MultiSet. + *

      + * Uniqueness constraints are the same as those in {@link java.util.Set}. + *

      + * The returned set is backed by this multiset, so any change to either + * is immediately reflected in the other. Only removal operations are + * supported, in which case all occurrences of the element are removed + * from the backing multiset. + * + * @return the Set of unique MultiSet elements + */ + Set uniqueSet(); + + /** + * Returns a {@link Set} of all entries contained in the MultiSet. + *

      + * The returned set is backed by this multiset, so any change to either + * is immediately reflected in the other. + * + * @return the Set of MultiSet entries + */ + Set> entrySet(); + + /** + * Returns an {@link Iterator} over the entire set of members, + * including copies due to cardinality. This iterator is fail-fast + * and will not tolerate concurrent modifications. + * + * @return iterator over all elements in the MultiSet + */ + @Override + Iterator iterator(); + + /** + * Returns the total number of items in the MultiSet. + * + * @return the total size of the multiset + */ + @Override + int size(); + + /** + * Returns true if the MultiSet contains at least one + * occurrence for each element contained in the given collection. + * + * @param coll the collection to check against + * @return true if the MultiSet contains all the collection + */ + @Override + boolean containsAll(Collection coll); + + /** + * Remove all occurrences of all elements from this MultiSet represented + * in the given collection. + * + * @param coll the collection of elements to remove + * @return true if this call changed the multiset + */ + @Override + boolean removeAll(Collection coll); + + /** + * Remove any elements of this MultiSet that are not contained in the + * given collection. + * + * @param coll the collection of elements to retain + * @return true if this call changed the multiset + */ + @Override + boolean retainAll(Collection coll); + + /** + * Compares this MultiSet to another object. + *

      + * This MultiSet equals another object if it is also a MultiSet + * that contains the same number of occurrences of the same elements. + * + * @param obj the object to compare to + * @return true if equal + */ + @Override + boolean equals(Object obj); + + /** + * Gets a hash code for the MultiSet compatible with the definition of equals. + * The hash code is defined as the sum total of a hash code for each element. + * The per element hash code is defined as + * (e==null ? 0 : e.hashCode()) ^ noOccurances). + * + * @return the hash code of the MultiSet + */ + @Override + int hashCode(); + + /** + * An unmodifiable entry for an element and its occurrence as contained in a MultiSet. + *

      + * The {@link MultiSet#entrySet()} method returns a view of the multiset whose elements + * implements this interface. + * + * @param the element type + */ + interface Entry { + + /** + * Returns the element corresponding to this entry. + * + * @return the element corresponding to this entry + */ + E getElement(); + + /** + * Returns the number of occurrences for the element of this entry. + * + * @return the number of occurrences of the element + */ + int getCount(); + + /** + * Compares the specified object with this entry for equality. + * Returns true if the given object is also a multiset entry + * and the two entries represent the same element with the same + * number of occurrences. + *

      + * More formally, two entries e1 and e2 represent + * the same mapping if + *

      +         *     (e1.getElement()==null ? e2.getElement()==null
      +         *                            : e1.getElement().equals(e2.getElement())) &&
      +         *     (e1.getCount()==e2.getCount())
      +         * 
      + * + * @param o object to be compared for equality with this multiset entry + * @return true if the specified object is equal to this multiset entry + */ + @Override + boolean equals(Object o); + + /** + * Returns the hash code value for this multiset entry. + *

      + * The hash code of a multiset entry e is defined to be: + *

      +         *      (e==null ? 0 : e.hashCode()) ^ noOccurances)
      +         * 
      + * + * @return the hash code value for this multiset entry + */ + @Override + int hashCode(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/MultiSetUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/MultiSetUtils.java new file mode 100644 index 000000000..ee4708856 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/MultiSetUtils.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import org.apache.commons.collections4.multiset.HashMultiSet; +import org.apache.commons.collections4.multiset.PredicatedMultiSet; +import org.apache.commons.collections4.multiset.SynchronizedMultiSet; +import org.apache.commons.collections4.multiset.UnmodifiableMultiSet; + +/** + * Provides utility methods and decorators for {@link MultiSet} instances. + * + * @since 4.1 + * @version $Id: MultiSetUtils.java 1714424 2015-11-15 10:06:16Z tn $ + */ +public class MultiSetUtils { + + /** + * An empty unmodifiable multiset. + */ + @SuppressWarnings("rawtypes") // OK, empty multiset is compatible with any type + public static final MultiSet EMPTY_MULTISET = + UnmodifiableMultiSet.unmodifiableMultiSet(new HashMultiSet()); + + /** + * Instantiation of MultiSetUtils is not intended or required. + */ + private MultiSetUtils() {} + + //----------------------------------------------------------------------- + /** + * Returns a synchronized (thread-safe) multiset backed by the given multiset. + * In order to guarantee serial access, it is critical that all access to the + * backing multiset is accomplished through the returned multiset. + *

      + * It is imperative that the user manually synchronize on the returned multiset + * when iterating over it: + * + *

      +     * MultiSet multiset = MultiSetUtils.synchronizedMultiSet(new HashMultiSet());
      +     * ...
      +     * synchronized(multiset) {
      +     *     Iterator i = multiset.iterator(); // Must be in synchronized block
      +     *     while (i.hasNext())
      +     *         foo(i.next());
      +     *     }
      +     * }
      +     * 
      + * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param the element type + * @param multiset the multiset to synchronize, must not be null + * @return a synchronized multiset backed by that multiset + * @throws NullPointerException if the MultiSet is null + */ + public static MultiSet synchronizedMultiSet(final MultiSet multiset) { + return SynchronizedMultiSet.synchronizedMultiSet(multiset); + } + + /** + * Returns an unmodifiable view of the given multiset. Any modification attempts + * to the returned multiset will raise an {@link UnsupportedOperationException}. + * + * @param the element type + * @param multiset the multiset whose unmodifiable view is to be returned, must not be null + * @return an unmodifiable view of that multiset + * @throws NullPointerException if the MultiSet is null + */ + public static MultiSet unmodifiableMultiSet(final MultiSet multiset) { + return UnmodifiableMultiSet.unmodifiableMultiSet(multiset); + } + + /** + * Returns a predicated (validating) multiset backed by the given multiset. + *

      + * Only objects that pass the test in the given predicate can be added to + * the multiset. Trying to add an invalid object results in an + * IllegalArgumentException. It is important not to use the original multiset + * after invoking this method, as it is a backdoor for adding invalid + * objects. + * + * @param the element type + * @param multiset the multiset to predicate, must not be null + * @param predicate the predicate for the multiset, must not be null + * @return a predicated multiset backed by the given multiset + * @throws NullPointerException if the MultiSet or Predicate is null + */ + public static MultiSet predicatedMultiSet(final MultiSet multiset, + final Predicate predicate) { + return PredicatedMultiSet.predicatedMultiSet(multiset, predicate); + } + + /** + * Get an empty MultiSet. + * + * @param the element type + * @return an empty MultiSet + */ + @SuppressWarnings("unchecked") // OK, empty multiset is compatible with any type + public static MultiSet emptyMultiSet() { + return EMPTY_MULTISET; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/MultiValuedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/MultiValuedMap.java new file mode 100644 index 000000000..1021822c5 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/MultiValuedMap.java @@ -0,0 +1,320 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Defines a map that holds a collection of values against each key. + *

      + * A {@code MultiValuedMap} is a Map with slightly different semantics: + *

        + *
      • Putting a value into the map will add the value to a {@link Collection} at that key.
      • + *
      • Getting a value will return a {@link Collection}, holding all the values put to that key.
      • + *
      + *

      + * For example: + *

      + * MultiValuedMap<K, String> map = new MultiValuedHashMap<K, String>();
      + * map.put(key, "A");
      + * map.put(key, "B");
      + * map.put(key, "C");
      + * Collection<String> coll = map.get(key);
      + * 
      + *

      + * coll will be a collection containing "A", "B", "C". + *

      + * + * @since 4.1 + * @version $Id: MultiValuedMap.java 1716537 2015-11-25 20:27:24Z tn $ + */ +public interface MultiValuedMap { + // Query operations + + /** + * Gets the total size of the map. + *

      + * Implementations would return the total size of the map which is the count + * of the values from all keys. + * + * @return the total size of the map + */ + int size(); + + /** + * Returns {@code true} if this map contains no key-value mappings. + * + * @return {@code true} if this map contains no key-value mappings + */ + boolean isEmpty(); + + /** + * Returns {@code true} if this map contains a mapping for the specified + * key. More formally, returns {@code true} if and only if this map contains + * a mapping for a key {@code k} such that {@code (key==null ? k==null : key.equals(k))}. + * (There can be at most one such mapping.) + * + * @param key key whose presence in this map is to be tested + * @return true if this map contains a mapping for the specified key + * @throws NullPointerException if the specified key is null and this map + * does not permit null keys (optional) + */ + boolean containsKey(Object key); + + /** + * Checks whether the map contains at least one mapping for the specified value. + * + * @param value the value to search for + * @return true if the map contains the value + * @throws NullPointerException if the value is null and null values are not supported + * by the used collection types (optional) + */ + boolean containsValue(Object value); + + /** + * Checks whether the map contains a mapping for the specified key and value. + * + * @param key the key to search for + * @param value the value to search for + * @return true if the map contains the value + */ + boolean containsMapping(Object key, Object value); + + /** + * Returns a view collection of the values associated with the specified key. + *

      + * This method will return an empty collection if {@link #containsKey(Object)} + * returns {@code false}. Changes to the returned collection will update the underlying + * {@code MultiValuedMap} and vice-versa. + * + * @param key the key to retrieve + * @return the {@code Collection} of values, implementations should + * return an empty collection for no mapping + * @throws NullPointerException if the key is null and null keys are invalid (optional) + */ + Collection get(K key); + + // Modification operations + + /** + * Adds a key-value mapping to this multi-valued map. + *

      + * Unlike a normal {@code Map} the previous value is not replaced. + * Instead the new value is added to the collection stored against the key. + * Depending on the collection type used, duplicate key-value mappings may + * be allowed. + *

      + * The method will return {@code true} if the size of the multi-valued map + * has been increased because of this operation. + * + * @param key the key to store against + * @param value the value to add to the collection at the key + * @return true if the map changed as a result of this put operation, or false + * if the map already contained the key-value mapping and the collection + * type does not allow duplicate values, e.g. when using a Set + * @throws UnsupportedOperationException if the put operation is not supported by + * this multi-valued map, e.g. if it is unmodifiable + * @throws NullPointerException if the key or value is null and null is invalid (optional) + * @throws IllegalArgumentException if some aspect of the specified key or value prevents + * it from being stored in this multi-valued map + */ + boolean put(K key, V value); + + /** + * Adds a mapping to the specified key for all values contained in the given Iterable. + * + * @param key the key to store against + * @param values the values to add to the collection at the key, may not be null + * @return true if the map changed as a result of this operation + * @throws NullPointerException if the specified iterable is null, or if this map + * does not permit null keys or values, and the specified key or values contain + * null (optional) + */ + boolean putAll(K key, Iterable values); + + /** + * Copies all mappings from the specified map to this multi-valued map + * (optional operation). + *

      + * The effect of this call is equivalent to that of calling + * {@link #put(Object,Object) put(k, v)} on this map once for each mapping + * from key {@code k} to value {@code v} in the specified map. + *

      + * The behavior of this operation is undefined if the specified map is modified + * while the operation is in progress. + * + * @param map mappings to be stored in this map, may not be null + * @return true if the map changed as a result of this operation + * @throws UnsupportedOperationException if the {@code putAll} operation is + * not supported by this map + * @throws NullPointerException if the specified map is null, or if this map + * does not permit null keys or values, and the specified map + * contains null keys or values (optional) + * @throws IllegalArgumentException if some property of a key or value in + * the specified map prevents it from being stored in this map + */ + boolean putAll(Map map); + + /** + * Copies all mappings from the specified map to this multi-valued map + * (optional operation). + *

      + * The effect of this call is equivalent to that of calling + * {@link #put(Object,Object) put(k, v)} on this map once for each + * mapping from key {@code k} to value {@code v} in the specified map. + *

      + * The behavior of this operation is undefined if the specified map is modified + * while the operation is in progress. + * + * @param map mappings to be stored in this map, may not be null + * @return true if the map changed as a result of this operation + * @throws UnsupportedOperationException if the {@code putAll} operation is + * not supported by this map + * @throws NullPointerException if the specified map is null, or if this map + * does not permit null keys or values, and the specified map + * contains null keys or values (optional) + * @throws IllegalArgumentException if some property of a key or value in + * the specified map prevents it from being stored in this map + */ + boolean putAll(MultiValuedMap map); + + /** + * Removes all values associated with the specified key. + *

      + * The returned collection may be modifiable, but updates will not be propagated + * to this multi-valued map. In case no mapping was stored for the specified + * key, an empty, unmodifiable collection will be returned. + * + * @param key the key to remove values from + * @return the values that were removed + * @throws UnsupportedOperationException if the map is unmodifiable + * @throws NullPointerException if the key is null and null keys are invalid (optional) + */ + Collection remove(Object key); + + /** + * Removes a key-value mapping from the map. + *

      + * The item is removed from the collection mapped to the specified key. + * Other values attached to that key are unaffected. + *

      + * If the last value for a key is removed, implementations typically return + * an empty collection from a subsequent get(Object). + * + * @param key the key to remove from + * @param item the item to remove + * @return true if the mapping was removed, false otherwise + * @throws UnsupportedOperationException if the map is unmodifiable + * @throws NullPointerException if the key or value is null and null is invalid (optional) + */ + boolean removeMapping(Object key, Object item); + + /** + * Removes all of the mappings from this map (optional operation). + *

      + * The map will be empty after this call returns. + * + * @throws UnsupportedOperationException if the map is unmodifiable + */ + void clear(); + + // Views + + /** + * Returns a {@link Collection} view of the mappings contained in this multi-valued map. + *

      + * The collection is backed by the map, so changes to the map are reflected + * in the collection, and vice-versa. + * + * @return a set view of the mappings contained in this map + */ + Collection> entries(); + + /** + * Returns a {@link MultiSet} view of the keys contained in this multi-valued map. + *

      + * The {@link MultiSet#getCount(Object)} method of the returned multiset will give + * the same result a calling {@code get(Object).size()} for the same key. + *

      + * This multiset is backed by the map, so any changes in the map are reflected in + * the multiset. + * + * @return a multiset view of the keys contained in this map + */ + MultiSet keys(); + + /** + * Returns a {@link Set} view of the keys contained in this multi-valued map. + *

      + * The set is backed by the map, so changes to the map are reflected + * in the set, and vice-versa. + *

      + * If the map is modified while an iteration over the set is in + * progress (except through the iterator's own {@code remove} operation), + * the result of the iteration is undefined. The set supports element + * removal, which removes the corresponding mapping from the map, via the + * {@code Iterator.remove}, {@code Set.remove}, {@code removeAll}, + * {@code retainAll}, and {@code clear} operations. It does not support + * the {@code add} or {@code addAll} operations. + * + * @return a set view of the keys contained in this map + */ + Set keySet(); + + /** + * Gets a {@link Collection} view of all values contained in this multi-valued map. + *

      + * Implementations typically return a collection containing the combination + * of values from all keys. + * + * @return a collection view of the values contained in this multi-valued map + */ + Collection values(); + + /** + * Returns a view of this multi-valued map as a {@code Map} from each distinct + * key to the non-empty collection of that key's associated values. + *

      + * Note that {@code this.asMap().get(k)} is equivalent to {@code this.get(k)} + * only when {@code k} is a key contained in the multi-valued map; otherwise it + * returns {@code null} as opposed to an empty collection. + *

      + * Changes to the returned map or the collections that serve as its values + * will update the underlying multi-valued map, and vice versa. The map does + * not support {@code put} or {@code putAll}, nor do its entries support + * {@link Map.Entry#setValue setValue}. + * + * @return a map view of the mappings in this multi-valued map + */ + Map> asMap(); + + // Iterators + + /** + * Obtains a MapIterator over this multi-valued map. + *

      + * A map iterator is an efficient way of iterating over maps. There is no + * need to access the entries collection or use {@code Map.Entry} objects. + * + * @return a map iterator + */ + MapIterator mapIterator(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/OrderedBidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/OrderedBidiMap.java new file mode 100644 index 000000000..1271c9e2f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/OrderedBidiMap.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Defines a map that allows bidirectional lookup between key and values + * and retains and provides access to an ordering. + *

      + * Implementations should allow a value to be looked up from a key and + * a key to be looked up from a value with equal performance. + * + * @param the type of the keys in the map + * @param the type of the values in the map + * + * @since 3.0 + * @version $Id: OrderedBidiMap.java 1543260 2013-11-19 00:47:22Z ggregory $ + */ +public interface OrderedBidiMap extends BidiMap, OrderedMap { + + /** + * Gets a view of this map where the keys and values are reversed. + *

      + * Changes to one map will be visible in the other and vice versa. + * This enables both directions of the map to be accessed equally. + *

      + * Implementations should seek to avoid creating a new object every time this + * method is called. See AbstractMap.values() etc. Calling this + * method on the inverse map should return the original. + *

      + * Implementations must return an OrderedBidiMap instance, + * usually by forwarding to inverseOrderedBidiMap(). + * + * @return an inverted bidirectional map + */ + OrderedBidiMap inverseBidiMap(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/OrderedIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/OrderedIterator.java new file mode 100644 index 000000000..095f49473 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/OrderedIterator.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Iterator; + +/** + * Defines an iterator that operates over an ordered container. Subset of {@link java.util.ListIterator}. + *

      + * This iterator allows both forward and reverse iteration through the container. + * + * @param the type to iterate over + * @since 3.0 + * @version $Id: OrderedIterator.java 1469004 2013-04-17 17:37:03Z tn $ + */ +public interface OrderedIterator extends Iterator { + + /** + * Checks to see if there is a previous element that can be iterated to. + * + * @return true if the iterator has a previous element + */ + boolean hasPrevious(); + + /** + * Gets the previous element from the container. + * + * @return the previous element in the iteration + * @throws java.util.NoSuchElementException if the iteration is finished + */ + E previous(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/OrderedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/OrderedMap.java new file mode 100644 index 000000000..b582a991e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/OrderedMap.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Defines a map that maintains order and allows both forward and backward + * iteration through that order. + * + * @param the type of the keys in the map + * @param the type of the values in the map + * + * @since 3.0 + * @version $Id: OrderedMap.java 1543259 2013-11-19 00:47:07Z ggregory $ + */ +public interface OrderedMap extends IterableMap { + + /** + * Obtains an OrderedMapIterator over the map. + *

      + * A ordered map iterator is an efficient way of iterating over maps + * in both directions. + * + * @return a map iterator + */ + OrderedMapIterator mapIterator(); + + /** + * Gets the first key currently in this map. + * + * @return the first key currently in this map + * @throws java.util.NoSuchElementException if this map is empty + */ + K firstKey(); + + /** + * Gets the last key currently in this map. + * + * @return the last key currently in this map + * @throws java.util.NoSuchElementException if this map is empty + */ + K lastKey(); + + /** + * Gets the next key after the one specified. + * + * @param key the key to search for next from + * @return the next key, null if no match or at end + */ + K nextKey(K key); + + /** + * Gets the previous key before the one specified. + * + * @param key the key to search for previous from + * @return the previous key, null if no match or at start + */ + K previousKey(K key); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/OrderedMapIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/OrderedMapIterator.java new file mode 100644 index 000000000..7e76ff274 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/OrderedMapIterator.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Defines an iterator that operates over an ordered Map. + *

      + * This iterator allows both forward and reverse iteration through the map. + * + * @param the type of the keys in the map + * @param the type of the values in the map + * @since 3.0 + * @version $Id: OrderedMapIterator.java 1469004 2013-04-17 17:37:03Z tn $ + */ +public interface OrderedMapIterator extends MapIterator, OrderedIterator { + + /** + * Checks to see if there is a previous entry that can be iterated to. + * + * @return true if the iterator has a previous element + */ + boolean hasPrevious(); + + /** + * Gets the previous key from the Map. + * + * @return the previous key in the iteration + * @throws java.util.NoSuchElementException if the iteration is finished + */ + K previous(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/Predicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/Predicate.java new file mode 100644 index 000000000..ce7df995a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/Predicate.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Defines a functor interface implemented by classes that perform a predicate + * test on an object. + *

      + * A Predicate is the object equivalent of an if statement. + * It uses the input object to return a true or false value, and is often used in + * validation or filtering. + *

      + * Standard implementations of common predicates are provided by + * {@link PredicateUtils}. These include true, false, instanceof, equals, and, + * or, not, method invokation and null testing. + * + * @param the type that the predicate queries + * + * @since 1.0 + * @version $Id: Predicate.java 1543262 2013-11-19 00:47:45Z ggregory $ + */ +public interface Predicate { + + /** + * Use the specified parameter to perform a test that returns true or false. + * + * @param object the object to evaluate, should not be changed + * @return true or false + * @throws ClassCastException (runtime) if the input is the wrong class + * @throws IllegalArgumentException (runtime) if the input is invalid + * @throws FunctorException (runtime) if the predicate encounters a problem + */ + boolean evaluate(T object); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/PredicateUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/PredicateUtils.java new file mode 100644 index 000000000..788ac8283 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/PredicateUtils.java @@ -0,0 +1,540 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; + +import org.apache.commons.collections4.functors.AllPredicate; +import org.apache.commons.collections4.functors.AndPredicate; +import org.apache.commons.collections4.functors.AnyPredicate; +import org.apache.commons.collections4.functors.EqualPredicate; +import org.apache.commons.collections4.functors.ExceptionPredicate; +import org.apache.commons.collections4.functors.FalsePredicate; +import org.apache.commons.collections4.functors.IdentityPredicate; +import org.apache.commons.collections4.functors.InstanceofPredicate; +import org.apache.commons.collections4.functors.InvokerTransformer; +import org.apache.commons.collections4.functors.NonePredicate; +import org.apache.commons.collections4.functors.NotNullPredicate; +import org.apache.commons.collections4.functors.NotPredicate; +import org.apache.commons.collections4.functors.NullIsExceptionPredicate; +import org.apache.commons.collections4.functors.NullIsFalsePredicate; +import org.apache.commons.collections4.functors.NullIsTruePredicate; +import org.apache.commons.collections4.functors.NullPredicate; +import org.apache.commons.collections4.functors.OnePredicate; +import org.apache.commons.collections4.functors.OrPredicate; +import org.apache.commons.collections4.functors.TransformedPredicate; +import org.apache.commons.collections4.functors.TransformerPredicate; +import org.apache.commons.collections4.functors.TruePredicate; +import org.apache.commons.collections4.functors.UniquePredicate; + +/** + * PredicateUtils provides reference implementations and utilities + * for the Predicate functor interface. The supplied predicates are: + *

        + *
      • Invoker - returns the result of a method call on the input object + *
      • InstanceOf - true if the object is an instanceof a class + *
      • Equal - true if the object equals() a specified object + *
      • Identity - true if the object == a specified object + *
      • Null - true if the object is null + *
      • NotNull - true if the object is not null + *
      • Unique - true if the object has not already been evaluated + *
      • And/All - true if all of the predicates are true + *
      • Or/Any - true if any of the predicates is true + *
      • Either/One - true if only one of the predicate is true + *
      • Neither/None - true if none of the predicates are true + *
      • Not - true if the predicate is false, and vice versa + *
      • Transformer - wraps a Transformer as a Predicate + *
      • True - always return true + *
      • False - always return false + *
      • Exception - always throws an exception + *
      • NullIsException/NullIsFalse/NullIsTrue - check for null input + *
      • Transformed - transforms the input before calling the predicate + *
      + * All the supplied predicates are Serializable. + * + * @since 3.0 + * @version $Id: PredicateUtils.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PredicateUtils { + + /** + * This class is not normally instantiated. + */ + private PredicateUtils() {} + + // Simple predicates + //----------------------------------------------------------------------------- + + /** + * Gets a Predicate that always throws an exception. + * This could be useful during testing as a placeholder. + * + * @param the type that the predicate queries + * @return the predicate + * @see ExceptionPredicate + */ + public static Predicate exceptionPredicate() { + return ExceptionPredicate.exceptionPredicate(); + } + + /** + * Gets a Predicate that always returns true. + * + * @param the type that the predicate queries + * @return the predicate + * @see TruePredicate + */ + public static Predicate truePredicate() { + return TruePredicate.truePredicate(); + } + + /** + * Gets a Predicate that always returns false. + * + * @param the type that the predicate queries + * @return the predicate + * @see FalsePredicate + */ + public static Predicate falsePredicate() { + return FalsePredicate.falsePredicate(); + } + + /** + * Gets a Predicate that checks if the input object passed in is null. + * + * @param the type that the predicate queries + * @return the predicate + * @see NullPredicate + */ + public static Predicate nullPredicate() { + return NullPredicate.nullPredicate(); + } + + /** + * Gets a Predicate that checks if the input object passed in is not null. + * + * @param the type that the predicate queries + * @return the predicate + * @see NotNullPredicate + */ + public static Predicate notNullPredicate() { + return NotNullPredicate.notNullPredicate(); + } + + /** + * Creates a Predicate that checks if the input object is equal to the + * specified object using equals(). + * + * @param the type that the predicate queries + * @param value the value to compare against + * @return the predicate + * @see EqualPredicate + */ + public static Predicate equalPredicate(final T value) { + return EqualPredicate.equalPredicate(value); + } + + /** + * Creates a Predicate that checks if the input object is equal to the + * specified object by identity. + * + * @param the type that the predicate queries + * @param value the value to compare against + * @return the predicate + * @see IdentityPredicate + */ + public static Predicate identityPredicate(final T value) { + return IdentityPredicate.identityPredicate(value); + } + + /** + * Creates a Predicate that checks if the object passed in is of + * a particular type, using instanceof. A null input + * object will return false. + * + * @param type the type to check for, may not be null + * @return the predicate + * @throws NullPointerException if the class is null + * @see InstanceofPredicate + */ + public static Predicate instanceofPredicate(final Class type) { + return InstanceofPredicate.instanceOfPredicate(type); + } + + /** + * Creates a Predicate that returns true the first time an object is + * encountered, and false if the same object is received + * again. The comparison is by equals(). A null input object + * is accepted and will return true the first time, and false subsequently + * as well. + * + * @param the type that the predicate queries + * @return the predicate + * @see UniquePredicate + */ + public static Predicate uniquePredicate() { + // must return new instance each time + return UniquePredicate.uniquePredicate(); + } + + /** + * Creates a Predicate that invokes a method on the input object. + * The method must return either a boolean or a non-null Boolean, + * and have no parameters. If the input object is null, a + * PredicateException is thrown. + *

      + * For example, PredicateUtils.invokerPredicate("isEmpty"); + * will call the isEmpty method on the input object to + * determine the predicate result. + * + * @param the type that the predicate queries + * @param methodName the method name to call on the input object, may not be null + * @return the predicate + * @throws NullPointerException if the methodName is null. + * @see InvokerTransformer + * @see TransformerPredicate + */ + public static Predicate invokerPredicate(final String methodName) { + // reuse transformer as it has caching - this is lazy really, should have inner class here + return asPredicate(InvokerTransformer.invokerTransformer(methodName)); + } + + /** + * Creates a Predicate that invokes a method on the input object. + * The method must return either a boolean or a non-null Boolean, + * and have no parameters. If the input object is null, a + * PredicateException is thrown. + *

      + * For example, PredicateUtils.invokerPredicate("isEmpty"); + * will call the isEmpty method on the input object to + * determine the predicate result. + * + * @param the type that the predicate queries + * @param methodName the method name to call on the input object, may not be null + * @param paramTypes the parameter types + * @param args the arguments + * @return the predicate + * @throws NullPointerException if the method name is null + * @throws IllegalArgumentException if the paramTypes and args don't match + * @see InvokerTransformer + * @see TransformerPredicate + */ + public static Predicate invokerPredicate(final String methodName, final Class[] paramTypes, + final Object[] args) { + // reuse transformer as it has caching - this is lazy really, should have inner class here + return asPredicate(InvokerTransformer.invokerTransformer(methodName, paramTypes, args)); + } + + // Boolean combinations + //----------------------------------------------------------------------------- + + /** + * Create a new Predicate that returns true only if both of the specified + * predicates are true. + * + * @param the type that the predicate queries + * @param predicate1 the first predicate, may not be null + * @param predicate2 the second predicate, may not be null + * @return the and predicate + * @throws NullPointerException if either predicate is null + * @see AndPredicate + */ + public static Predicate andPredicate(final Predicate predicate1, + final Predicate predicate2) { + return AndPredicate.andPredicate(predicate1, predicate2); + } + + /** + * Create a new Predicate that returns true only if all of the specified + * predicates are true. + * If the array of predicates is empty, then this predicate returns true. + * + * @param the type that the predicate queries + * @param predicates an array of predicates to check, may not be null + * @return the all predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + * @see AllPredicate + */ + public static Predicate allPredicate(final Predicate... predicates) { + return AllPredicate.allPredicate(predicates); + } + + /** + * Create a new Predicate that returns true only if all of the specified + * predicates are true. The predicates are checked in iterator order. + * If the collection of predicates is empty, then this predicate returns true. + * + * @param the type that the predicate queries + * @param predicates a collection of predicates to check, may not be null + * @return the all predicate + * @throws NullPointerException if the predicates collection is null + * @throws NullPointerException if any predicate in the collection is null + * @see AllPredicate + */ + public static Predicate allPredicate(final Collection> predicates) { + return AllPredicate.allPredicate(predicates); + } + + /** + * Create a new Predicate that returns true if either of the specified + * predicates are true. + * + * @param the type that the predicate queries + * @param predicate1 the first predicate, may not be null + * @param predicate2 the second predicate, may not be null + * @return the or predicate + * @throws NullPointerException if either predicate is null + * @see OrPredicate + */ + public static Predicate orPredicate(final Predicate predicate1, + final Predicate predicate2) { + return OrPredicate.orPredicate(predicate1, predicate2); + } + + /** + * Create a new Predicate that returns true if any of the specified + * predicates are true. + * If the array of predicates is empty, then this predicate returns false. + * + * @param the type that the predicate queries + * @param predicates an array of predicates to check, may not be null + * @return the any predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + * @see AnyPredicate + */ + public static Predicate anyPredicate(final Predicate... predicates) { + return AnyPredicate.anyPredicate(predicates); + } + + /** + * Create a new Predicate that returns true if any of the specified + * predicates are true. The predicates are checked in iterator order. + * If the collection of predicates is empty, then this predicate returns false. + * + * @param the type that the predicate queries + * @param predicates a collection of predicates to check, may not be null + * @return the any predicate + * @throws NullPointerException if the predicates collection is null + * @throws NullPointerException if any predicate in the collection is null + * @see AnyPredicate + */ + public static Predicate anyPredicate(final Collection> predicates) { + return AnyPredicate.anyPredicate(predicates); + } + + /** + * Create a new Predicate that returns true if one, but not both, of the + * specified predicates are true. XOR + * + * @param the type that the predicate queries + * @param predicate1 the first predicate, may not be null + * @param predicate2 the second predicate, may not be null + * @return the either predicate + * @throws NullPointerException if either predicate is null + * @see OnePredicate + */ + public static Predicate eitherPredicate(final Predicate predicate1, + final Predicate predicate2) { + @SuppressWarnings("unchecked") + final Predicate onePredicate = PredicateUtils.onePredicate(predicate1, predicate2); + return onePredicate; + } + + /** + * Create a new Predicate that returns true if only one of the specified + * predicates are true. + * If the array of predicates is empty, then this predicate returns false. + * + * @param the type that the predicate queries + * @param predicates an array of predicates to check, may not be null + * @return the one predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + * @see OnePredicate + */ + public static Predicate onePredicate(final Predicate... predicates) { + return OnePredicate.onePredicate(predicates); + } + + /** + * Create a new Predicate that returns true if only one of the specified + * predicates are true. The predicates are checked in iterator order. + * If the collection of predicates is empty, then this predicate returns false. + * + * @param the type that the predicate queries + * @param predicates a collection of predicates to check, may not be null + * @return the one predicate + * @throws NullPointerException if the predicates collection is null + * @throws NullPointerException if any predicate in the collection is null + * @see OnePredicate + */ + public static Predicate onePredicate(final Collection> predicates) { + return OnePredicate.onePredicate(predicates); + } + + /** + * Create a new Predicate that returns true if neither of the specified + * predicates are true. + * + * @param the type that the predicate queries + * @param predicate1 the first predicate, may not be null + * @param predicate2 the second predicate, may not be null + * @return the neither predicate + * @throws NullPointerException if either predicate is null + * @see NonePredicate + */ + public static Predicate neitherPredicate(final Predicate predicate1, + final Predicate predicate2) { + @SuppressWarnings("unchecked") + final Predicate nonePredicate = PredicateUtils.nonePredicate(predicate1, predicate2); + return nonePredicate; + } + + /** + * Create a new Predicate that returns true if none of the specified + * predicates are true. + * If the array of predicates is empty, then this predicate returns true. + * + * @param the type that the predicate queries + * @param predicates an array of predicates to check, may not be null + * @return the none predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + * @see NonePredicate + */ + public static Predicate nonePredicate(final Predicate... predicates) { + return NonePredicate.nonePredicate(predicates); + } + + /** + * Create a new Predicate that returns true if none of the specified + * predicates are true. The predicates are checked in iterator order. + * If the collection of predicates is empty, then this predicate returns true. + * + * @param the type that the predicate queries + * @param predicates a collection of predicates to check, may not be null + * @return the none predicate + * @throws NullPointerException if the predicates collection is null + * @throws NullPointerException if any predicate in the collection is null + * @see NonePredicate + */ + public static Predicate nonePredicate(final Collection> predicates) { + return NonePredicate.nonePredicate(predicates); + } + + /** + * Create a new Predicate that returns true if the specified predicate + * returns false and vice versa. + * + * @param the type that the predicate queries + * @param predicate the predicate to not + * @return the not predicate + * @throws NullPointerException if the predicate is null + * @see NotPredicate + */ + public static Predicate notPredicate(final Predicate predicate) { + return NotPredicate.notPredicate(predicate); + } + + // Adaptors + //----------------------------------------------------------------------------- + + /** + * Create a new Predicate that wraps a Transformer. The Transformer must + * return either Boolean.TRUE or Boolean.FALSE otherwise a PredicateException + * will be thrown. + * + * @param the type that the predicate queries + * @param transformer the transformer to wrap, may not be null + * @return the transformer wrapping predicate + * @throws NullPointerException if the transformer is null + * @see TransformerPredicate + */ + public static Predicate asPredicate(final Transformer transformer) { + return TransformerPredicate.transformerPredicate(transformer); + } + + // Null handlers + //----------------------------------------------------------------------------- + + /** + * Gets a Predicate that throws an exception if the input object is null, + * otherwise it calls the specified Predicate. This allows null handling + * behaviour to be added to Predicates that don't support nulls. + * + * @param the type that the predicate queries + * @param predicate the predicate to wrap, may not be null + * @return the predicate + * @throws NullPointerException if the predicate is null. + * @see NullIsExceptionPredicate + */ + public static Predicate nullIsExceptionPredicate(final Predicate predicate){ + return NullIsExceptionPredicate.nullIsExceptionPredicate(predicate); + } + + /** + * Gets a Predicate that returns false if the input object is null, otherwise + * it calls the specified Predicate. This allows null handling behaviour to + * be added to Predicates that don't support nulls. + * + * @param the type that the predicate queries + * @param predicate the predicate to wrap, may not be null + * @return the predicate + * @throws NullPointerException if the predicate is null. + * @see NullIsFalsePredicate + */ + public static Predicate nullIsFalsePredicate(final Predicate predicate){ + return NullIsFalsePredicate.nullIsFalsePredicate(predicate); + } + + /** + * Gets a Predicate that returns true if the input object is null, otherwise + * it calls the specified Predicate. This allows null handling behaviour to + * be added to Predicates that don't support nulls. + * + * @param the type that the predicate queries + * @param predicate the predicate to wrap, may not be null + * @return the predicate + * @throws NullPointerException if the predicate is null. + * @see NullIsTruePredicate + */ + public static Predicate nullIsTruePredicate(final Predicate predicate){ + return NullIsTruePredicate.nullIsTruePredicate(predicate); + } + + // Transformed + //----------------------------------------------------------------------- + /** + * Creates a predicate that transforms the input object before passing it + * to the predicate. + * + * @param the type that the predicate queries + * @param transformer the transformer to call first + * @param predicate the predicate to call with the result of the transform + * @return the predicate + * @throws NullPointerException if the transformer or the predicate is null + * @see TransformedPredicate + * @since 3.1 + */ + public static Predicate transformedPredicate( + final Transformer transformer, final Predicate predicate) { + return TransformedPredicate.transformedPredicate(transformer, predicate); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/Put.java b/fine-commons-collections4/src/org/apache/commons/collections4/Put.java new file mode 100644 index 000000000..7fe31651d --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/Put.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Map; + +/** + * The "write" subset of the {@link Map} interface. + *

      + * NOTE: in the original {@link Map} interface, {@link Map#put(Object, Object)} is known + * to have the same return type as {@link Map#get(Object)}, namely {@code V}. {@link Put} + * makes no assumptions in this regard (there is no association with, nor even knowledge + * of, a "reading" interface) and thus defines {@link #put(Object, Object)} as returning + * {@link Object}. + * + * @since 4.0 + * @version $Id: Put.java 1543257 2013-11-19 00:45:55Z ggregory $ + * + * @see Get + */ +public interface Put { + + /** + * @see Map#clear() + */ + void clear(); + + /** + * Note that the return type is Object, rather than V as in the Map interface. + * See the class Javadoc for further info. + * + * @see Map#put(Object, Object) + */ + Object put(K key, V value); + + /** + * @see Map#putAll(Map) + */ + void putAll(Map t); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/QueueUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/QueueUtils.java new file mode 100644 index 000000000..04d319141 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/QueueUtils.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.LinkedList; +import java.util.Queue; + +import org.apache.commons.collections4.queue.PredicatedQueue; +import org.apache.commons.collections4.queue.TransformedQueue; +import org.apache.commons.collections4.queue.UnmodifiableQueue; + +/** + * Provides utility methods and decorators for {@link Queue} instances. + * + * @since 4.0 + * @version $Id: QueueUtils.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class QueueUtils { + + /** + * An empty unmodifiable queue. + */ + @SuppressWarnings("rawtypes") // OK, empty queue is compatible with any type + public static final Queue EMPTY_QUEUE = UnmodifiableQueue.unmodifiableQueue(new LinkedList()); + + /** + * QueueUtils should not normally be instantiated. + */ + private QueueUtils() {} + + //----------------------------------------------------------------------- + + /** + * Returns an unmodifiable queue backed by the given queue. + * + * @param the type of the elements in the queue + * @param queue the queue to make unmodifiable, must not be null + * @return an unmodifiable queue backed by that queue + * @throws NullPointerException if the queue is null + */ + public static Queue unmodifiableQueue(final Queue queue) { + return UnmodifiableQueue.unmodifiableQueue(queue); + } + + /** + * Returns a predicated (validating) queue backed by the given queue. + *

      + * Only objects that pass the test in the given predicate can be added to the queue. + * Trying to add an invalid object results in an IllegalArgumentException. + * It is important not to use the original queue after invoking this method, + * as it is a backdoor for adding invalid objects. + * + * @param the type of the elements in the queue + * @param queue the queue to predicate, must not be null + * @param predicate the predicate used to evaluate new elements, must not be null + * @return a predicated queue + * @throws NullPointerException if the queue or predicate is null + */ + public static Queue predicatedQueue(final Queue queue, final Predicate predicate) { + return PredicatedQueue.predicatedQueue(queue, predicate); + } + + /** + * Returns a transformed queue backed by the given queue. + *

      + * Each object is passed through the transformer as it is added to the + * Queue. It is important not to use the original queue after invoking this + * method, as it is a backdoor for adding untransformed objects. + *

      + * Existing entries in the specified queue will not be transformed. + * If you want that behaviour, see {@link TransformedQueue#transformedQueue}. + * + * @param the type of the elements in the queue + * @param queue the queue to predicate, must not be null + * @param transformer the transformer for the queue, must not be null + * @return a transformed queue backed by the given queue + * @throws NullPointerException if the queue or transformer is null + */ + public static Queue transformingQueue(final Queue queue, + final Transformer transformer) { + return TransformedQueue.transformingQueue(queue, transformer); + } + + /** + * Get an empty Queue. + * + * @param the type of the elements in the queue + * @return an empty {@link Queue} + */ + @SuppressWarnings("unchecked") // OK, empty queue is compatible with any type + public static Queue emptyQueue() { + return (Queue) EMPTY_QUEUE; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/ResettableIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/ResettableIterator.java new file mode 100644 index 000000000..b3f810539 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/ResettableIterator.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Iterator; + +/** + * Defines an iterator that can be reset back to an initial state. + *

      + * This interface allows an iterator to be repeatedly reused. + * + * @param the type to iterate over + * @since 3.0 + * @version $Id: ResettableIterator.java 1543263 2013-11-19 00:47:55Z ggregory $ + */ +public interface ResettableIterator extends Iterator { + + /** + * Resets the iterator back to the position at which the iterator + * was created. + */ + void reset(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/ResettableListIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/ResettableListIterator.java new file mode 100644 index 000000000..5fcac2dd8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/ResettableListIterator.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.ListIterator; + +/** + * Defines a list iterator that can be reset back to an initial state. + *

      + * This interface allows an iterator to be repeatedly reused. + * + * @param the type to iterate over + * @since 3.0 + * @version $Id: ResettableListIterator.java 1477779 2013-04-30 18:55:24Z tn $ + */ +public interface ResettableListIterator extends ListIterator, ResettableIterator, OrderedIterator { + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/SetUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/SetUtils.java new file mode 100644 index 000000000..b110fb0c3 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/SetUtils.java @@ -0,0 +1,645 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.commons.collections4.set.ListOrderedSet; +import org.apache.commons.collections4.set.PredicatedNavigableSet; +import org.apache.commons.collections4.set.PredicatedSet; +import org.apache.commons.collections4.set.PredicatedSortedSet; +import org.apache.commons.collections4.set.TransformedNavigableSet; +import org.apache.commons.collections4.set.TransformedSet; +import org.apache.commons.collections4.set.TransformedSortedSet; +import org.apache.commons.collections4.set.UnmodifiableNavigableSet; +import org.apache.commons.collections4.set.UnmodifiableSet; +import org.apache.commons.collections4.set.UnmodifiableSortedSet; + +/** + * Provides utility methods and decorators for + * {@link Set} and {@link SortedSet} instances. + * + * @since 2.1 + * @version $Id: SetUtils.java 1686950 2015-06-22 21:51:07Z tn $ + */ +public class SetUtils { + + /** + * Get a typed empty unmodifiable Set. + * @param the element type + * @return an empty Set + */ + public static Set emptySet() { + return Collections.emptySet(); + } + + /** + * An empty unmodifiable sorted set. + * This is not provided in the JDK. + */ + @SuppressWarnings("rawtypes") + public static final SortedSet EMPTY_SORTED_SET = + UnmodifiableSortedSet.unmodifiableSortedSet(new TreeSet()); + + /** + * Get a typed empty unmodifiable sorted set. + * @param the element type + * @return an empty sorted Set + */ + @SuppressWarnings("unchecked") // empty set is OK for any type + public static SortedSet emptySortedSet() { + return EMPTY_SORTED_SET; + } + + /** + * SetUtils should not normally be instantiated. + */ + private SetUtils() {} + + //----------------------------------------------------------------------- + + /** + * Returns an immutable empty set if the argument is null, + * or the argument itself otherwise. + * + * @param the element type + * @param set the set, possibly null + * @return an empty set if the argument is null + */ + public static Set emptyIfNull(final Set set) { + return set == null ? Collections.emptySet() : set; + } + + /** + * Tests two sets for equality as per the equals() contract + * in {@link java.util.Set#equals(java.lang.Object)}. + *

      + * This method is useful for implementing Set when you cannot + * extend AbstractSet. The method takes Collection instances to enable other + * collection types to use the Set implementation algorithm. + *

      + * The relevant text (slightly paraphrased as this is a static method) is: + *

      + *

      Two sets are considered equal if they have + * the same size, and every member of the first set is contained in + * the second. This ensures that the {@code equals} method works + * properly across different implementations of the {@code Set} + * interface.

      + * + *

      + * This implementation first checks if the two sets are the same object: + * if so it returns {@code true}. Then, it checks if the two sets are + * identical in size; if not, it returns false. If so, it returns + * {@code a.containsAll((Collection) b)}.

      + *
      + * + * @see java.util.Set + * @param set1 the first set, may be null + * @param set2 the second set, may be null + * @return whether the sets are equal by value comparison + */ + public static boolean isEqualSet(final Collection set1, final Collection set2) { + if (set1 == set2) { + return true; + } + if (set1 == null || set2 == null || set1.size() != set2.size()) { + return false; + } + + return set1.containsAll(set2); + } + + /** + * Generates a hash code using the algorithm specified in + * {@link java.util.Set#hashCode()}. + *

      + * This method is useful for implementing Set when you cannot + * extend AbstractSet. The method takes Collection instances to enable other + * collection types to use the Set implementation algorithm. + * + * @param the element type + * @see java.util.Set#hashCode() + * @param set the set to calculate the hash code for, may be null + * @return the hash code + */ + public static int hashCodeForSet(final Collection set) { + if (set == null) { + return 0; + } + + int hashCode = 0; + for (final T obj : set) { + if (obj != null) { + hashCode += obj.hashCode(); + } + } + return hashCode; + } + + /** + * Returns a new hash set that matches elements based on == not + * equals(). + *

      + * This set will violate the detail of various Set contracts. + * As a general rule, don't compare this set to other sets. In particular, you can't + * use decorators like {@link ListOrderedSet} on it, which silently assume that these + * contracts are fulfilled. + *

      + * Note that the returned set is not synchronized and is not thread-safe. + * If you wish to use this set from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedSet(Set)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + * + * @param the element type + * @return a new identity hash set + * @since 4.1 + */ + public static Set newIdentityHashSet() { + return Collections.newSetFromMap(new IdentityHashMap()); + } + + // Set + //----------------------------------------------------------------------- + /** + * Returns a synchronized set backed by the given set. + *

      + * You must manually synchronize on the returned set's iterator to + * avoid non-deterministic behavior: + * + *

      +     * Set s = SetUtils.synchronizedSet(mySet);
      +     * synchronized (s) {
      +     *     Iterator i = s.iterator();
      +     *     while (i.hasNext()) {
      +     *         process (i.next());
      +     *     }
      +     * }
      +     * 
      + * + * This method is just a wrapper for {@link Collections#synchronizedSet(Set)}. + * + * @param the element type + * @param set the set to synchronize, must not be null + * @return a synchronized set backed by the given set + * @throws NullPointerException if the set is null + */ + public static Set synchronizedSet(final Set set) { + return Collections.synchronizedSet(set); + } + + /** + * Returns an unmodifiable set backed by the given set. + *

      + * This method uses the implementation in the decorators subpackage. + * + * @param the element type + * @param set the set to make unmodifiable, must not be null + * @return an unmodifiable set backed by the given set + * @throws NullPointerException if the set is null + */ + public static Set unmodifiableSet(final Set set) { + return UnmodifiableSet.unmodifiableSet(set); + } + + /** + * Returns a predicated (validating) set backed by the given set. + *

      + * Only objects that pass the test in the given predicate can be added to the set. + * Trying to add an invalid object results in an IllegalArgumentException. + * It is important not to use the original set after invoking this method, + * as it is a backdoor for adding invalid objects. + * + * @param the element type + * @param set the set to predicate, must not be null + * @param predicate the predicate for the set, must not be null + * @return a predicated set backed by the given set + * @throws NullPointerException if the set or predicate is null + */ + public static Set predicatedSet(final Set set, final Predicate predicate) { + return PredicatedSet.predicatedSet(set, predicate); + } + + /** + * Returns a transformed set backed by the given set. + *

      + * Each object is passed through the transformer as it is added to the + * Set. It is important not to use the original set after invoking this + * method, as it is a backdoor for adding untransformed objects. + *

      + * Existing entries in the specified set will not be transformed. + * If you want that behaviour, see {@link TransformedSet#transformedSet}. + * + * @param the element type + * @param set the set to transform, must not be null + * @param transformer the transformer for the set, must not be null + * @return a transformed set backed by the given set + * @throws NullPointerException if the set or transformer is null + */ + public static Set transformedSet(final Set set, + final Transformer transformer) { + return TransformedSet.transformingSet(set, transformer); + } + + /** + * Returns a set that maintains the order of elements that are added + * backed by the given set. + *

      + * If an element is added twice, the order is determined by the first add. + * The order is observed through the iterator or toArray. + * + * @param the element type + * @param set the set to order, must not be null + * @return an ordered set backed by the given set + * @throws NullPointerException if the set is null + */ + public static Set orderedSet(final Set set) { + return ListOrderedSet.listOrderedSet(set); + } + + // SortedSet + //----------------------------------------------------------------------- + /** + * Returns a synchronized sorted set backed by the given sorted set. + *

      + * You must manually synchronize on the returned set's iterator to + * avoid non-deterministic behavior: + * + *

      +     * Set s = SetUtils.synchronizedSortedSet(mySet);
      +     * synchronized (s) {
      +     *     Iterator i = s.iterator();
      +     *     while (i.hasNext()) {
      +     *         process (i.next());
      +     *     }
      +     * }
      +     * 
      + * + * This method is just a wrapper for {@link Collections#synchronizedSortedSet(SortedSet)}. + * + * @param the element type + * @param set the sorted set to synchronize, must not be null + * @return a synchronized set backed by the given set + * @throws NullPointerException if the set is null + */ + public static SortedSet synchronizedSortedSet(final SortedSet set) { + return Collections.synchronizedSortedSet(set); + } + + /** + * Returns an unmodifiable sorted set backed by the given sorted set. + *

      + * This method uses the implementation in the decorators subpackage. + * + * @param the element type + * @param set the sorted set to make unmodifiable, must not be null + * @return an unmodifiable set backed by the given set + * @throws NullPointerException if the set is null + */ + public static SortedSet unmodifiableSortedSet(final SortedSet set) { + return UnmodifiableSortedSet.unmodifiableSortedSet(set); + } + + /** + * Returns a predicated (validating) sorted set backed by the given sorted set. + *

      + * Only objects that pass the test in the given predicate can be added to the set. + * Trying to add an invalid object results in an IllegalArgumentException. + * It is important not to use the original set after invoking this method, + * as it is a backdoor for adding invalid objects. + * + * @param the element type + * @param set the sorted set to predicate, must not be null + * @param predicate the predicate for the sorted set, must not be null + * @return a predicated sorted set backed by the given sorted set + * @throws NullPointerException if the set or predicate is null + */ + public static SortedSet predicatedSortedSet(final SortedSet set, + final Predicate predicate) { + return PredicatedSortedSet.predicatedSortedSet(set, predicate); + } + + /** + * Returns a transformed sorted set backed by the given set. + *

      + * Each object is passed through the transformer as it is added to the + * Set. It is important not to use the original set after invoking this + * method, as it is a backdoor for adding untransformed objects. + *

      + * Existing entries in the specified set will not be transformed. + * If you want that behaviour, see {@link TransformedSortedSet#transformedSortedSet}. + * + * @param the element type + * @param set the set to transform, must not be null + * @param transformer the transformer for the set, must not be null + * @return a transformed set backed by the given set + * @throws NullPointerException if the set or transformer is null + */ + public static SortedSet transformedSortedSet(final SortedSet set, + final Transformer transformer) { + return TransformedSortedSet.transformingSortedSet(set, transformer); + } + + // NavigableSet + //----------------------------------------------------------------------- + /** + * Returns an unmodifiable navigable set backed by the given navigable set. + *

      + * This method uses the implementation in the decorators subpackage. + * + * @param the element type + * @param set the navigable set to make unmodifiable, must not be null + * @return an unmodifiable set backed by the given set + * @throws NullPointerException if the set is null + * @since 4.1 + */ + public static SortedSet unmodifiableNavigableSet(final NavigableSet set) { + return UnmodifiableNavigableSet.unmodifiableNavigableSet(set); + } + + /** + * Returns a predicated (validating) navigable set backed by the given navigable set. + *

      + * Only objects that pass the test in the given predicate can be added to the set. + * Trying to add an invalid object results in an IllegalArgumentException. + * It is important not to use the original set after invoking this method, + * as it is a backdoor for adding invalid objects. + * + * @param the element type + * @param set the navigable set to predicate, must not be null + * @param predicate the predicate for the navigable set, must not be null + * @return a predicated navigable set backed by the given navigable set + * @throws NullPointerException if the set or predicate is null + * @since 4.1 + */ + public static SortedSet predicatedNavigableSet(final NavigableSet set, + final Predicate predicate) { + return PredicatedNavigableSet.predicatedNavigableSet(set, predicate); + } + + /** + * Returns a transformed navigable set backed by the given navigable set. + *

      + * Each object is passed through the transformer as it is added to the + * Set. It is important not to use the original set after invoking this + * method, as it is a backdoor for adding untransformed objects. + *

      + * Existing entries in the specified set will not be transformed. + * If you want that behaviour, see {@link TransformedNavigableSet#transformedNavigableSet}. + * + * @param the element type + * @param set the navigable set to transform, must not be null + * @param transformer the transformer for the set, must not be null + * @return a transformed set backed by the given set + * @throws NullPointerException if the set or transformer is null + * @since 4.1 + */ + public static SortedSet transformedNavigableSet(final NavigableSet set, + final Transformer transformer) { + return TransformedNavigableSet.transformingNavigableSet(set, transformer); + } + + // Set operations + //----------------------------------------------------------------------- + + /** + * Returns a unmodifiable view of the union of the given {@link Set}s. + *

      + * The returned view contains all elements of {@code a} and {@code b}. + * + * @param the generic type that is able to represent the types contained + * in both input sets. + * @param a the first set, must not be null + * @param b the second set, must not be null + * @return a view of the union of the two set + * @throws NullPointerException if either input set is null + * @since 4.1 + */ + public static SetView union(final Set a, final Set b) { + if (a == null || b == null) { + throw new NullPointerException("Sets must not be null."); + } + + final SetView bMinusA = difference(b, a); + + return new SetView() { + @Override + public boolean contains(Object o) { + return a.contains(o) || b.contains(o); + } + + @Override + public Iterator createIterator() { + return IteratorUtils.chainedIterator(a.iterator(), bMinusA.iterator()); + } + + @Override + public boolean isEmpty() { + return a.isEmpty() && b.isEmpty(); + } + + @Override + public int size() { + return a.size() + bMinusA.size(); + } + }; + } + + /** + * Returns a unmodifiable view containing the difference of the given + * {@link Set}s, denoted by {@code a \ b} (or {@code a - b}). + *

      + * The returned view contains all elements of {@code a} that are not a member + * of {@code b}. + * + * @param the generic type that is able to represent the types contained + * in both input sets. + * @param a the set to subtract from, must not be null + * @param b the set to subtract, must not be null + * @return a view of the relative complement of of the two sets + * @since 4.1 + */ + public static SetView difference(final Set a, final Set b) { + if (a == null || b == null) { + throw new NullPointerException("Sets must not be null."); + } + + final Predicate notContainedInB = new Predicate() { + @Override + public boolean evaluate(E object) { + return !b.contains(object); + } + }; + + return new SetView() { + @Override + public boolean contains(Object o) { + return a.contains(o) && !b.contains(o); + } + + @Override + public Iterator createIterator() { + return IteratorUtils.filteredIterator(a.iterator(), notContainedInB); + } + }; + } + + /** + * Returns a unmodifiable view of the intersection of the given {@link Set}s. + *

      + * The returned view contains all elements that are members of both input sets + * ({@code a} and {@code b}). + * + * @param the generic type that is able to represent the types contained + * in both input sets. + * @param a the first set, must not be null + * @param b the second set, must not be null + * @return a view of the intersection of the two sets + * @since 4.1 + */ + public static SetView intersection(final Set a, final Set b) { + if (a == null || b == null) { + throw new NullPointerException("Sets must not be null."); + } + + final Predicate containedInB = new Predicate() { + @Override + public boolean evaluate(E object) { + return b.contains(object); + } + }; + + return new SetView() { + @Override + public boolean contains(Object o) { + return a.contains(o) && b.contains(o); + } + + @Override + public Iterator createIterator() { + return IteratorUtils.filteredIterator(a.iterator(), containedInB); + } + }; + } + + /** + * Returns a unmodifiable view of the symmetric difference of the given + * {@link Set}s. + *

      + * The returned view contains all elements of {@code a} and {@code b} that are + * not a member of the other set. + *

      + * This is equivalent to {@code union(difference(a, b), difference(b, a))}. + * + * @param the generic type that is able to represent the types contained + * in both input sets. + * @param a the first set, must not be null + * @param b the second set, must not be null + * @return a view of the symmetric difference of the two sets + * @since 4.1 + */ + public static SetView disjunction(final Set a, final Set b) { + if (a == null || b == null) { + throw new NullPointerException("Sets must not be null."); + } + + final SetView aMinusB = difference(a, b); + final SetView bMinusA = difference(b, a); + + return new SetView() { + @Override + public boolean contains(Object o) { + return a.contains(o) ^ b.contains(o); + } + + @Override + public Iterator createIterator() { + return IteratorUtils.chainedIterator(aMinusB.iterator(), bMinusA.iterator()); + } + + @Override + public boolean isEmpty() { + return aMinusB.isEmpty() && bMinusA.isEmpty(); + } + + @Override + public int size() { + return aMinusB.size() + bMinusA.size(); + } + }; + } + + /** + * An unmodifiable view of a set that may be backed by other sets. + *

      + * If the decorated sets change, this view will change as well. The contents + * of this view can be transferred to another instance via the {@link #copyInto(Set)} + * and {@link #toSet()} methods. + * + * @param the element type + * @since 4.1 + */ + public static abstract class SetView extends AbstractSet { + + @Override + public Iterator iterator() { + return IteratorUtils.unmodifiableIterator(createIterator()); + } + + /** + * Return an iterator for this view; the returned iterator is + * not required to be unmodifiable. + * @return a new iterator for this view + */ + protected abstract Iterator createIterator(); + + @Override + public int size() { + return IteratorUtils.size(iterator()); + } + + /** + * Copies the contents of this view into the provided set. + * + * @param the set type + * @param set the set for copying the contents + */ + public > void copyInto(final S set) { + CollectionUtils.addAll(set, this); + } + + /** + * Returns a new set containing the contents of this view. + * + * @return a new set containing all elements of this view + */ + public Set toSet() { + final Set set = new HashSet(size()); + copyInto(set); + return set; + } + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/SetValuedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/SetValuedMap.java new file mode 100644 index 000000000..faf10173f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/SetValuedMap.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Set; + +/** + * Defines a map that holds a set of values against each key. + *

      + * A {@code SetValuedMap} is a Map with slightly different semantics: + *

        + *
      • Putting a value into the map will add the value to a {@link Set} at that key.
      • + *
      • Getting a value will return a {@link Set}, holding all the values put to that key.
      • + *
      + * + * @since 4.1 + * @version $Id: SetValuedMap.java 1685299 2015-06-13 18:27:11Z tn $ + */ +public interface SetValuedMap extends MultiValuedMap { + + /** + * Gets the set of values associated with the specified key. + *

      + * Implementations typically return an empty {@code Set} if no values + * have been mapped to the key. + *

      + * + * @param key the key to retrieve + * @return the {@code Set} of values, implementations should return an + * empty {@code Set} for no mapping + * @throws NullPointerException if the key is null and null keys are invalid + */ + @Override + Set get(K key); + + /** + * Removes all values associated with the specified key. + *

      + * The returned set may be modifiable, but updates will not be + * propagated to this set-valued map. In case no mapping was stored for the + * specified key, an empty, unmodifiable set will be returned. + * + * @param key the key to remove values from + * @return the {@code Set} of values removed, implementations should + * return null for no mapping found, but may return an empty collection + * @throws UnsupportedOperationException if the map is unmodifiable + * @throws NullPointerException if the key is null and null keys are invalid + */ + @Override + Set remove(Object key); +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/SortedBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/SortedBag.java new file mode 100644 index 000000000..b38a27d0c --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/SortedBag.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Comparator; + +/** + * Defines a type of Bag that maintains a sorted order among + * its unique representative members. + * + * @param the type to iterate over + * @since 2.0 + * @version $Id: SortedBag.java 1543264 2013-11-19 00:48:12Z ggregory $ + */ +public interface SortedBag extends Bag { + + /** + * Returns the comparator associated with this sorted set, or null + * if it uses its elements' natural ordering. + * + * @return the comparator in use, or null if natural ordering + */ + Comparator comparator(); + + /** + * Returns the first (lowest) member. + * + * @return the first element in the sorted bag + */ + E first(); + + /** + * Returns the last (highest) member. + * + * @return the last element in the sorted bag + */ + E last(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/SortedBidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/SortedBidiMap.java new file mode 100644 index 000000000..115692ff9 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/SortedBidiMap.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Comparator; +import java.util.SortedMap; + +/** + * Defines a map that allows bidirectional lookup between key and values + * and retains both keys and values in sorted order. + *

      + * Implementations should allow a value to be looked up from a key and + * a key to be looked up from a value with equal performance. + * + * @param the type of the keys in the map + * @param the type of the values in the map + * @since 3.0 + * @version $Id: SortedBidiMap.java 1543255 2013-11-19 00:45:24Z ggregory $ + */ +public interface SortedBidiMap extends OrderedBidiMap, SortedMap { + + /** + * Gets a view of this map where the keys and values are reversed. + *

      + * Changes to one map will be visible in the other and vice versa. + * This enables both directions of the map to be accessed equally. + *

      + * Implementations should seek to avoid creating a new object every time this + * method is called. See AbstractMap.values() etc. Calling this + * method on the inverse map should return the original. + *

      + * Implementations must return a SortedBidiMap instance, + * usually by forwarding to inverseSortedBidiMap(). + * + * @return an inverted bidirectional map + */ + SortedBidiMap inverseBidiMap(); + + /** + * Get the comparator used for the values in the value-to-key map aspect. + * @return Comparator + */ + Comparator valueComparator(); +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/SplitMapUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/SplitMapUtils.java new file mode 100644 index 000000000..49830d466 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/SplitMapUtils.java @@ -0,0 +1,247 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.collection.UnmodifiableCollection; +import org.apache.commons.collections4.iterators.UnmodifiableMapIterator; +import org.apache.commons.collections4.map.EntrySetToMapIteratorAdapter; +import org.apache.commons.collections4.map.UnmodifiableEntrySet; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Utilities for working with "split maps:" objects that implement {@link Put} + * and/or {@link Get} but not {@link Map}. + * + * @since 4.0 + * @version $Id: SplitMapUtils.java 1686855 2015-06-22 13:00:27Z tn $ + * + * @see Get + * @see Put + */ +public class SplitMapUtils { + + /** + * SplitMapUtils should not normally be instantiated. + */ + private SplitMapUtils() {} + + //----------------------------------------------------------------------- + + private static class WrappedGet implements IterableMap, Unmodifiable { + private final Get get; + + private WrappedGet(final Get get) { + this.get = get; + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public boolean containsKey(final Object key) { + return get.containsKey(key); + } + + public boolean containsValue(final Object value) { + return get.containsValue(value); + } + + public Set> entrySet() { + return UnmodifiableEntrySet.unmodifiableEntrySet(get.entrySet()); + } + + @Override + public boolean equals(final Object arg0) { + if (arg0 == this) { + return true; + } + return arg0 instanceof WrappedGet && ((WrappedGet) arg0).get.equals(this.get); + } + + public V get(final Object key) { + return get.get(key); + } + + @Override + public int hashCode() { + return ("WrappedGet".hashCode() << 4) | get.hashCode(); + } + + public boolean isEmpty() { + return get.isEmpty(); + } + + public Set keySet() { + return UnmodifiableSet.unmodifiableSet(get.keySet()); + } + + public V put(final K key, final V value) { + throw new UnsupportedOperationException(); + } + + public void putAll(final Map t) { + throw new UnsupportedOperationException(); + } + + public V remove(final Object key) { + return get.remove(key); + } + + public int size() { + return get.size(); + } + + public Collection values() { + return UnmodifiableCollection.unmodifiableCollection(get.values()); + } + + public MapIterator mapIterator() { + MapIterator it; + if (get instanceof IterableGet) { + it = ((IterableGet) get).mapIterator(); + } else { + it = new EntrySetToMapIteratorAdapter(get.entrySet()); + } + return UnmodifiableMapIterator.unmodifiableMapIterator(it); + } + } + + private static class WrappedPut implements Map, Put { + private final Put put; + + private WrappedPut(final Put put) { + this.put = put; + } + + public void clear() { + put.clear(); + } + + public boolean containsKey(final Object key) { + throw new UnsupportedOperationException(); + } + + public boolean containsValue(final Object value) { + throw new UnsupportedOperationException(); + } + + public Set> entrySet() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + return obj instanceof WrappedPut && ((WrappedPut) obj).put.equals(this.put); + } + + public V get(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public int hashCode() { + return ("WrappedPut".hashCode() << 4) | put.hashCode(); + } + + public boolean isEmpty() { + throw new UnsupportedOperationException(); + } + + public Set keySet() { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + public V put(final K key, final V value) { + return (V) put.put(key, value); + } + + public void putAll(final Map t) { + put.putAll(t); + } + + public V remove(final Object key) { + throw new UnsupportedOperationException(); + } + + public int size() { + throw new UnsupportedOperationException(); + } + + public Collection values() { + throw new UnsupportedOperationException(); + } + } + + //----------------------------------------------------------------------- + + /** + * Get the specified {@link Get} as an instance of {@link IterableMap}. + * If get implements {@link IterableMap} directly, no conversion will take place. + * If get implements {@link Map} but not {@link IterableMap} it will be decorated. + * Otherwise an {@link Unmodifiable} {@link IterableMap} will be returned. + * @param the key type + * @param the value type + * @param get to wrap, must not be null + * @return {@link IterableMap} + * @throws NullPointerException if the argument is null + */ + @SuppressWarnings("unchecked") + public static IterableMap readableMap(final Get get) { + if (get == null) { + throw new NullPointerException("Get must not be null"); + } + if (get instanceof Map) { + return get instanceof IterableMap ? + ((IterableMap) get) : + MapUtils.iterableMap((Map) get); + } + return new WrappedGet(get); + } + + /** + * Get the specified {@link Put} as an instanceof {@link Map}. + * If put implements {@link Map} directly, no conversion will take place. + * Otherwise a write-only {@link Map} will be returned. On such a {@link Map} + * it is recommended that the result of #put(K, V) be discarded as it likely will not + * match V at runtime. + * + * @param the key type + * @param the element type + * @param put to wrap, must not be null + * @return {@link Map} + * @throws NullPointerException if the argument is null + */ + @SuppressWarnings("unchecked") + public static Map writableMap(final Put put) { + if (put == null) { + throw new NullPointerException("Put must not be null"); + } + if (put instanceof Map) { + return (Map) put; + } + return new WrappedPut(put); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/Transformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/Transformer.java new file mode 100644 index 000000000..138f6472e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/Transformer.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Defines a functor interface implemented by classes that transform one + * object into another. + *

      + * A Transformer converts the input object to the output object. + * The input object should be left unchanged. + * Transformers are typically used for type conversions, or extracting data + * from an object. + *

      + * Standard implementations of common transformers are provided by + * {@link TransformerUtils}. These include method invocation, returning a constant, + * cloning and returning the string value. + * + * @param the input type to the transformer + * @param the output type from the transformer + * + * @since 1.0 + * @version $Id: Transformer.java 1543278 2013-11-19 00:54:07Z ggregory $ + */ +public interface Transformer { + + /** + * Transforms the input object (leaving it unchanged) into some output object. + * + * @param input the object to be transformed, should be left unchanged + * @return a transformed object + * @throws ClassCastException (runtime) if the input is the wrong class + * @throws IllegalArgumentException (runtime) if the input is invalid + * @throws FunctorException (runtime) if the transform cannot be completed + */ + O transform(I input); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/TransformerUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/TransformerUtils.java new file mode 100644 index 000000000..96caa0f0f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/TransformerUtils.java @@ -0,0 +1,484 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.Collection; +import java.util.Map; + +import org.apache.commons.collections4.functors.ChainedTransformer; +import org.apache.commons.collections4.functors.CloneTransformer; +import org.apache.commons.collections4.functors.ClosureTransformer; +import org.apache.commons.collections4.functors.ConstantTransformer; +import org.apache.commons.collections4.functors.EqualPredicate; +import org.apache.commons.collections4.functors.ExceptionTransformer; +import org.apache.commons.collections4.functors.FactoryTransformer; +import org.apache.commons.collections4.functors.IfTransformer; +import org.apache.commons.collections4.functors.InstantiateTransformer; +import org.apache.commons.collections4.functors.InvokerTransformer; +import org.apache.commons.collections4.functors.MapTransformer; +import org.apache.commons.collections4.functors.NOPTransformer; +import org.apache.commons.collections4.functors.PredicateTransformer; +import org.apache.commons.collections4.functors.StringValueTransformer; +import org.apache.commons.collections4.functors.SwitchTransformer; + +/** + * TransformerUtils provides reference implementations and + * utilities for the Transformer functor interface. The supplied transformers are: + *

        + *
      • Invoker - returns the result of a method call on the input object + *
      • Clone - returns a clone of the input object + *
      • Constant - always returns the same object + *
      • Closure - performs a Closure and returns the input object + *
      • Predicate - returns the result of the predicate as a Boolean + *
      • Factory - returns a new object from a factory + *
      • Chained - chains two or more transformers together + *
      • If - calls one transformer or another based on a predicate + *
      • Switch - calls one transformer based on one or more predicates + *
      • SwitchMap - calls one transformer looked up from a Map + *
      • Instantiate - the Class input object is instantiated + *
      • Map - returns an object from a supplied Map + *
      • Null - always returns null + *
      • NOP - returns the input object, which should be immutable + *
      • Exception - always throws an exception + *
      • StringValue - returns a java.lang.String representation of the input object + *
      + *

      + * Since v4.1 only transformers which are considered to be unsafe are + * Serializable. Transformers considered to be unsafe for serialization are: + *

        + *
      • Invoker + *
      • Clone + *
      • Instantiate + *
      + * + * @since 3.0 + * @version $Id: TransformerUtils.java 1714362 2015-11-14 20:38:02Z tn $ + */ +public class TransformerUtils { + + /** + * This class is not normally instantiated. + */ + private TransformerUtils() {} + + /** + * Gets a transformer that always throws an exception. + * This could be useful during testing as a placeholder. + * + * @param the input type + * @param the output type + * @return the transformer + * @see ExceptionTransformer + */ + public static Transformer exceptionTransformer() { + return ExceptionTransformer.exceptionTransformer(); + } + + /** + * Gets a transformer that always returns null. + * + * @param the input type + * @param the output type + * @return the transformer + * @see ConstantTransformer + */ + public static Transformer nullTransformer() { + return ConstantTransformer.nullTransformer(); + } + + /** + * Gets a transformer that returns the input object. + * The input object should be immutable to maintain the + * contract of Transformer (although this is not checked). + * + * @param the input/output type + * @return the transformer + * @see NOPTransformer + */ + public static Transformer nopTransformer() { + return NOPTransformer.nopTransformer(); + } + + /** + * Gets a transformer that returns a clone of the input object. + * The input object will be cloned using one of these techniques (in order): + *
        + *
      • public clone method + *
      • public copy constructor + *
      • serialization clone + *
          + * + * @param the input/output type + * @return the transformer + * @see CloneTransformer + */ + public static Transformer cloneTransformer() { + return CloneTransformer.cloneTransformer(); + } + + /** + * Creates a Transformer that will return the same object each time the + * transformer is used. + * + * @param the input type + * @param the output type + * @param constantToReturn the constant object to return each time in the transformer + * @return the transformer. + * @see ConstantTransformer + */ + public static Transformer constantTransformer(final O constantToReturn) { + return ConstantTransformer.constantTransformer(constantToReturn); + } + + /** + * Creates a Transformer that calls a Closure each time the transformer is used. + * The transformer returns the input object. + * + * @param the input/output type + * @param closure the closure to run each time in the transformer, not null + * @return the transformer + * @throws NullPointerException if the closure is null + * @see ClosureTransformer + */ + public static Transformer asTransformer(final Closure closure) { + return ClosureTransformer.closureTransformer(closure); + } + + /** + * Creates a Transformer that calls a Predicate each time the transformer is used. + * The transformer will return either Boolean.TRUE or Boolean.FALSE. + * + * @param the input type + * @param predicate the predicate to run each time in the transformer, not null + * @return the transformer + * @throws NullPointerException if the predicate is null + * @see PredicateTransformer + */ + public static Transformer asTransformer(final Predicate predicate) { + return PredicateTransformer.predicateTransformer(predicate); + } + + /** + * Creates a Transformer that calls a Factory each time the transformer is used. + * The transformer will return the value returned by the factory. + * + * @param the input type + * @param the output type + * @param factory the factory to run each time in the transformer, not null + * @return the transformer + * @throws NullPointerException if the factory is null + * @see FactoryTransformer + */ + public static Transformer asTransformer(final Factory factory) { + return FactoryTransformer.factoryTransformer(factory); + } + + /** + * Create a new Transformer that calls each transformer in turn, passing the + * result into the next transformer. + * + * @param the input/output type + * @param transformers an array of transformers to chain + * @return the transformer + * @throws NullPointerException if the transformers array or any of the transformers is null + * @see ChainedTransformer + */ + public static Transformer chainedTransformer( + final Transformer... transformers) { + return ChainedTransformer.chainedTransformer(transformers); + } + + /** + * Create a new Transformer that calls each transformer in turn, passing the + * result into the next transformer. The ordering is that of the iterator() + * method on the collection. + * + * @param the input/output type + * @param transformers a collection of transformers to chain + * @return the transformer + * @throws NullPointerException if the transformers collection or any of the transformers is null + * @see ChainedTransformer + */ + public static Transformer chainedTransformer( + final Collection> transformers) { + return ChainedTransformer.chainedTransformer(transformers); + } + + /** + * Create a new Transformer that calls the transformer if the predicate is true, + * otherwise the input object is returned unchanged. + * + * @param the input / output type + * @param predicate the predicate to switch on + * @param trueTransformer the transformer called if the predicate is true + * @return the transformer + * @throws NullPointerException if either the predicate or transformer is null + * @see IfTransformer + * @since 4.1 + */ + public static Transformer ifTransformer(final Predicate predicate, + final Transformer trueTransformer) { + return IfTransformer.ifTransformer(predicate, trueTransformer); + } + + /** + * Create a new Transformer that calls one of two transformers depending + * on the specified predicate. + * + * @param the input type + * @param the output type + * @param predicate the predicate to switch on + * @param trueTransformer the transformer called if the predicate is true + * @param falseTransformer the transformer called if the predicate is false + * @return the transformer + * @throws NullPointerException if either the predicate or transformer is null + * @see IfTransformer + * @since 4.1 + */ + public static Transformer ifTransformer(final Predicate predicate, + final Transformer trueTransformer, + final Transformer falseTransformer) { + return IfTransformer.ifTransformer(predicate, trueTransformer, falseTransformer); + } + + /** + * Create a new Transformer that calls one of two transformers depending + * on the specified predicate. + * + * @param the input type + * @param the output type + * @param predicate the predicate to switch on + * @param trueTransformer the transformer called if the predicate is true + * @param falseTransformer the transformer called if the predicate is false + * @return the transformer + * @throws NullPointerException if either the predicate or transformer is null + * @see SwitchTransformer + * @deprecated as of 4.1, use {@link #ifTransformer(Predicate, Transformer, Transformer)) + */ + @SuppressWarnings("unchecked") + @Deprecated + public static Transformer switchTransformer(final Predicate predicate, + final Transformer trueTransformer, + final Transformer falseTransformer) { + return SwitchTransformer.switchTransformer(new Predicate[] { predicate }, + new Transformer[] { trueTransformer }, falseTransformer); + } + + /** + * Create a new Transformer that calls one of the transformers depending + * on the predicates. The transformer at array location 0 is called if the + * predicate at array location 0 returned true. Each predicate is evaluated + * until one returns true. If no predicates evaluate to true, null is returned. + * + * @param the input type + * @param the output type + * @param predicates an array of predicates to check + * @param transformers an array of transformers to call + * @return the transformer + * @throws NullPointerException if the either array is null + * @throws NullPointerException if any element in the arrays is null + * @throws IllegalArgumentException if the arrays have different sizes + * @see SwitchTransformer + */ + public static Transformer switchTransformer(final Predicate[] predicates, + final Transformer[] transformers) { + return SwitchTransformer.switchTransformer(predicates, transformers, null); + } + + /** + * Create a new Transformer that calls one of the transformers depending + * on the predicates. The transformer at array location 0 is called if the + * predicate at array location 0 returned true. Each predicate is evaluated + * until one returns true. If no predicates evaluate to true, the default + * transformer is called. If the default transformer is null, null is returned. + * + * @param the input type + * @param the output type + * @param predicates an array of predicates to check + * @param transformers an array of transformers to call + * @param defaultTransformer the default to call if no predicate matches, null means return null + * @return the transformer + * @throws NullPointerException if the either array is null + * @throws NullPointerException if any element in the arrays is null + * @throws IllegalArgumentException if the arrays have different sizes + * @see SwitchTransformer + */ + public static Transformer switchTransformer(final Predicate[] predicates, + final Transformer[] transformers, + final Transformer defaultTransformer) { + return SwitchTransformer.switchTransformer(predicates, transformers, defaultTransformer); + } + + /** + * Create a new Transformer that calls one of the transformers depending + * on the predicates. + *

          + * The Map consists of Predicate keys and Transformer values. A transformer + * is called if its matching predicate returns true. Each predicate is evaluated + * until one returns true. If no predicates evaluate to true, the default + * transformer is called. The default transformer is set in the map with a + * null key. If no default transformer is set, null will be returned in a default + * case. The ordering is that of the iterator() method on the entryset collection + * of the map. + * + * @param the input type + * @param the output type + * @param predicatesAndTransformers a map of predicates to transformers + * @return the transformer + * @throws NullPointerException if the map is null + * @throws NullPointerException if any transformer in the map is null + * @throws ClassCastException if the map elements are of the wrong type + * @see SwitchTransformer + */ + public static Transformer switchTransformer( + final Map, Transformer> predicatesAndTransformers) { + return SwitchTransformer.switchTransformer(predicatesAndTransformers); + } + + /** + * Create a new Transformer that uses the input object as a key to find the + * transformer to call. + *

          + * The Map consists of object keys and Transformer values. A transformer + * is called if the input object equals the key. If there is no match, the + * default transformer is called. The default transformer is set in the map + * using a null key. If no default is set, null will be returned in a default case. + * + * @param the input type + * @param the output type + * @param objectsAndTransformers a map of objects to transformers + * @return the transformer + * @throws NullPointerException if the map is null + * @throws NullPointerException if any transformer in the map is null + * @see SwitchTransformer + */ + @SuppressWarnings("unchecked") + public static Transformer switchMapTransformer( + final Map> objectsAndTransformers) { + + if (objectsAndTransformers == null) { + throw new NullPointerException("The object and transformer map must not be null"); + } + final Transformer def = objectsAndTransformers.remove(null); + final int size = objectsAndTransformers.size(); + final Transformer[] trs = new Transformer[size]; + final Predicate[] preds = new Predicate[size]; + int i = 0; + for (final Map.Entry> entry : objectsAndTransformers.entrySet()) { + preds[i] = EqualPredicate.equalPredicate(entry.getKey()); + trs[i++] = entry.getValue(); + } + return TransformerUtils.switchTransformer(preds, trs, def); + } + + /** + * Gets a Transformer that expects an input Class object that it will instantiate. + * + * @param the output type + * @return the transformer + * @see InstantiateTransformer + */ + public static Transformer, T> instantiateTransformer() { + return InstantiateTransformer.instantiateTransformer(); + } + + /** + * Creates a Transformer that expects an input Class object that it will + * instantiate. The constructor used is determined by the arguments specified + * to this method. + * + * @param the output type + * @param paramTypes parameter types for the constructor, can be null + * @param args the arguments to pass to the constructor, can be null + * @return the transformer + * @throws IllegalArgumentException if the paramTypes and args don't match + * @see InstantiateTransformer + */ + public static Transformer, T> instantiateTransformer( + final Class[] paramTypes, final Object[] args) { + return InstantiateTransformer.instantiateTransformer(paramTypes, args); + } + + /** + * Creates a Transformer that uses the passed in Map to transform the input + * object (as a simple lookup). + * + * @param the input type + * @param the output type + * @param map the map to use to transform the objects + * @return the transformer, or {@link ConstantTransformer#nullTransformer()} if the + * {@code map} is {@code null} + * @see MapTransformer + */ + public static Transformer mapTransformer(final Map map) { + return MapTransformer.mapTransformer(map); + } + + /** + * Gets a Transformer that invokes a method on the input object. + * The method must have no parameters. If the input object is null, + * null is returned. + *

          + * For example, TransformerUtils.invokerTransformer("getName"); + * will call the getName/code> method on the input object to + * determine the transformer result. + * + * @param the input type + * @param the output type + * @param methodName the method name to call on the input object, may not be null + * @return the transformer + * @throws NullPointerException if the methodName is null. + * @see InvokerTransformer + */ + public static Transformer invokerTransformer(final String methodName) { + return InvokerTransformer.invokerTransformer(methodName, null, null); + } + + /** + * Gets a Transformer that invokes a method on the input object. + * The method parameters are specified. If the input object is {@code null}, + * {@code null} is returned. + * + * @param the input type + * @param the output type + * @param methodName the name of the method + * @param paramTypes the parameter types + * @param args the arguments + * @return the transformer + * @throws NullPointerException if the method name is null + * @throws IllegalArgumentException if the paramTypes and args don't match + * @see InvokerTransformer + */ + public static Transformer invokerTransformer(final String methodName, final Class[] paramTypes, + final Object[] args) { + return InvokerTransformer.invokerTransformer(methodName, paramTypes, args); + } + + /** + * Gets a transformer that returns a java.lang.String + * representation of the input object. This is achieved via the + * toString method, null returns 'null'. + * + * @param the input type + * @return the transformer + * @see StringValueTransformer + */ + public static Transformer stringValueTransformer() { + return StringValueTransformer.stringValueTransformer(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/Trie.java b/fine-commons-collections4/src/org/apache/commons/collections4/Trie.java new file mode 100644 index 000000000..fe70e00af --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/Trie.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import java.util.SortedMap; + +/** + * Defines the interface for a prefix tree, an ordered tree data structure. For + * more information, see Tries. + * + * @since 4.0 + * @version $Id: Trie.java 1543279 2013-11-19 00:54:31Z ggregory $ + */ +public interface Trie extends IterableSortedMap { + + /** + * Returns a view of this {@link Trie} of all elements that are prefixed + * by the given key. + *

          + * In a {@link Trie} with fixed size keys, this is essentially a + * {@link #get(Object)} operation. + *

          + * For example, if the {@link Trie} contains 'Anna', 'Anael', + * 'Analu', 'Andreas', 'Andrea', 'Andres', and 'Anatole', then + * a lookup of 'And' would return 'Andreas', 'Andrea', and 'Andres'. + * + * @param key the key used in the search + * @return a {@link SortedMap} view of this {@link Trie} with all elements whose + * key is prefixed by the search key + */ + SortedMap prefixMap(K key); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/TrieUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/TrieUtils.java new file mode 100644 index 000000000..baf5186e2 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/TrieUtils.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +import org.apache.commons.collections4.trie.UnmodifiableTrie; + +/** + * A collection of {@link Trie} utilities. + * + * @since 4.0 + * @version $Id: TrieUtils.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TrieUtils { + + /** + * {@link TrieUtils} should not normally be instantiated. + */ + private TrieUtils() {} + + /** + * Returns an unmodifiable instance of a {@link Trie} + * + * @param the key type + * @param the value type + * @param trie the trie to make unmodifiable, must not be null + * @return an unmodifiable trie backed by the given trie + * @throws NullPointerException if trie is null + * + * @see java.util.Collections#unmodifiableMap(java.util.Map) + */ + public static Trie unmodifiableTrie(final Trie trie) { + return UnmodifiableTrie.unmodifiableTrie(trie); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/Unmodifiable.java b/fine-commons-collections4/src/org/apache/commons/collections4/Unmodifiable.java new file mode 100644 index 000000000..1799462fa --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/Unmodifiable.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4; + +/** + * Marker interface for collections, maps and iterators that are unmodifiable. + *

          + * This interface enables testing such as: + *

          + * if (coll instanceof Unmodifiable) {
          + *   coll = new ArrayList(coll);
          + * }
          + * // now we know coll is modifiable
          + * 
          + * Of course all this only works if you use the Unmodifiable classes defined + * in this library. If you use the JDK unmodifiable class via {@code java.util Collections} + * then the interface won't be there. + * + * @since 3.0 + * @version $Id: Unmodifiable.java 1477779 2013-04-30 18:55:24Z tn $ + */ +public interface Unmodifiable { + // marker interface - no methods to implement +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractBagDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractBagDecorator.java new file mode 100644 index 000000000..ed0d6d30f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractBagDecorator.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.util.Set; + +import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.collection.AbstractCollectionDecorator; + +/** + * Decorates another Bag to provide additional behaviour. + *

          + * Methods are forwarded directly to the decorated bag. + * + * @since 3.0 + * @version $Id: AbstractBagDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractBagDecorator + extends AbstractCollectionDecorator implements Bag { + + /** Serialization version */ + private static final long serialVersionUID = -3768146017343785417L; + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since 3.1 + */ + protected AbstractBagDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param bag the bag to decorate, must not be null + * @throws NullPointerException if bag is null + */ + protected AbstractBagDecorator(final Bag bag) { + super(bag); + } + + /** + * Gets the bag being decorated. + * + * @return the decorated bag + */ + @Override + protected Bag decorated() { + return (Bag) super.decorated(); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + //----------------------------------------------------------------------- + + @Override + public int getCount(final Object object) { + return decorated().getCount(object); + } + + @Override + public boolean add(final E object, final int count) { + return decorated().add(object, count); + } + + @Override + public boolean remove(final Object object, final int count) { + return decorated().remove(object, count); + } + + @Override + public Set uniqueSet() { + return decorated().uniqueSet(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractMapBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractMapBag.java new file mode 100644 index 000000000..a271a5f3c --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractMapBag.java @@ -0,0 +1,624 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Abstract implementation of the {@link Bag} interface to simplify the creation + * of subclass implementations. + *

          + * Subclasses specify a Map implementation to use as the internal storage. The + * map will be used to map bag elements to a number; the number represents the + * number of occurrences of that element in the bag. + * + * @since 3.0 (previously DefaultMapBag v2.0) + * @version $Id: AbstractMapBag.java 1684859 2015-06-11 11:57:24Z tn $ + */ +public abstract class AbstractMapBag implements Bag { + + /** The map to use to store the data */ + private transient Map map; + /** The current total size of the bag */ + private int size; + /** The modification count for fail fast iterators */ + private transient int modCount; + /** Unique view of the elements */ + private transient Set uniqueSet; + + /** + * Constructor needed for subclass serialisation. + */ + protected AbstractMapBag() { + super(); + } + + /** + * Constructor that assigns the specified Map as the backing store. The map + * must be empty and non-null. + * + * @param map the map to assign + */ + protected AbstractMapBag(final Map map) { + super(); + this.map = map; + } + + /** + * Utility method for implementations to access the map that backs this bag. + * Not intended for interactive use outside of subclasses. + * + * @return the map being used by the Bag + */ + protected Map getMap() { + return map; + } + + //----------------------------------------------------------------------- + /** + * Returns the number of elements in this bag. + * + * @return current size of the bag + */ + @Override + public int size() { + return size; + } + + /** + * Returns true if the underlying map is empty. + * + * @return true if bag is empty + */ + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * Returns the number of occurrence of the given element in this bag by + * looking up its count in the underlying map. + * + * @param object the object to search for + * @return the number of occurrences of the object, zero if not found + */ + @Override + public int getCount(final Object object) { + final MutableInteger count = map.get(object); + if (count != null) { + return count.value; + } + return 0; + } + + //----------------------------------------------------------------------- + /** + * Determines if the bag contains the given element by checking if the + * underlying map contains the element as a key. + * + * @param object the object to search for + * @return true if the bag contains the given element + */ + @Override + public boolean contains(final Object object) { + return map.containsKey(object); + } + + /** + * Determines if the bag contains the given elements. + * + * @param coll the collection to check against + * @return true if the Bag contains all the collection + */ + @Override + public boolean containsAll(final Collection coll) { + if (coll instanceof Bag) { + return containsAll((Bag) coll); + } + return containsAll(new HashBag(coll)); + } + + /** + * Returns true if the bag contains all elements in the given + * collection, respecting cardinality. + * + * @param other the bag to check against + * @return true if the Bag contains all the collection + */ + boolean containsAll(final Bag other) { + final Iterator it = other.uniqueSet().iterator(); + while (it.hasNext()) { + final Object current = it.next(); + if (getCount(current) < other.getCount(current)) { + return false; + } + } + return true; + } + + //----------------------------------------------------------------------- + /** + * Gets an iterator over the bag elements. Elements present in the Bag more + * than once will be returned repeatedly. + * + * @return the iterator + */ + @Override + public Iterator iterator() { + return new BagIterator(this); + } + + /** + * Inner class iterator for the Bag. + */ + static class BagIterator implements Iterator { + private final AbstractMapBag parent; + private final Iterator> entryIterator; + private Map.Entry current; + private int itemCount; + private final int mods; + private boolean canRemove; + + /** + * Constructor. + * + * @param parent the parent bag + */ + public BagIterator(final AbstractMapBag parent) { + this.parent = parent; + this.entryIterator = parent.map.entrySet().iterator(); + this.current = null; + this.mods = parent.modCount; + this.canRemove = false; + } + + /** {@inheritDoc} */ + @Override + public boolean hasNext() { + return itemCount > 0 || entryIterator.hasNext(); + } + + /** {@inheritDoc} */ + @Override + public E next() { + if (parent.modCount != mods) { + throw new ConcurrentModificationException(); + } + if (itemCount == 0) { + current = entryIterator.next(); + itemCount = current.getValue().value; + } + canRemove = true; + itemCount--; + return current.getKey(); + } + + /** {@inheritDoc} */ + @Override + public void remove() { + if (parent.modCount != mods) { + throw new ConcurrentModificationException(); + } + if (canRemove == false) { + throw new IllegalStateException(); + } + final MutableInteger mut = current.getValue(); + if (mut.value > 1) { + mut.value--; + } else { + entryIterator.remove(); + } + parent.size--; + canRemove = false; + } + } + + //----------------------------------------------------------------------- + /** + * Adds a new element to the bag, incrementing its count in the underlying map. + * + * @param object the object to add + * @return true if the object was not already in the uniqueSet + */ + @Override + public boolean add(final E object) { + return add(object, 1); + } + + /** + * Adds a new element to the bag, incrementing its count in the map. + * + * @param object the object to search for + * @param nCopies the number of copies to add + * @return true if the object was not already in the uniqueSet + */ + @Override + public boolean add(final E object, final int nCopies) { + modCount++; + if (nCopies > 0) { + final MutableInteger mut = map.get(object); + size += nCopies; + if (mut == null) { + map.put(object, new MutableInteger(nCopies)); + return true; + } + mut.value += nCopies; + return false; + } + return false; + } + + /** + * Invokes {@link #add(Object)} for each element in the given collection. + * + * @param coll the collection to add + * @return true if this call changed the bag + */ + @Override + public boolean addAll(final Collection coll) { + boolean changed = false; + final Iterator i = coll.iterator(); + while (i.hasNext()) { + final boolean added = add(i.next()); + changed = changed || added; + } + return changed; + } + + //----------------------------------------------------------------------- + /** + * Clears the bag by clearing the underlying map. + */ + @Override + public void clear() { + modCount++; + map.clear(); + size = 0; + } + + /** + * Removes all copies of the specified object from the bag. + * + * @param object the object to remove + * @return true if the bag changed + */ + @Override + public boolean remove(final Object object) { + final MutableInteger mut = map.get(object); + if (mut == null) { + return false; + } + modCount++; + map.remove(object); + size -= mut.value; + return true; + } + + /** + * Removes a specified number of copies of an object from the bag. + * + * @param object the object to remove + * @param nCopies the number of copies to remove + * @return true if the bag changed + */ + @Override + public boolean remove(final Object object, final int nCopies) { + final MutableInteger mut = map.get(object); + if (mut == null) { + return false; + } + if (nCopies <= 0) { + return false; + } + modCount++; + if (nCopies < mut.value) { + mut.value -= nCopies; + size -= nCopies; + } else { + map.remove(object); + size -= mut.value; + } + return true; + } + + /** + * Removes objects from the bag according to their count in the specified + * collection. + * + * @param coll the collection to use + * @return true if the bag changed + */ + @Override + public boolean removeAll(final Collection coll) { + boolean result = false; + if (coll != null) { + final Iterator i = coll.iterator(); + while (i.hasNext()) { + final boolean changed = remove(i.next(), 1); + result = result || changed; + } + } + return result; + } + + /** + * Remove any members of the bag that are not in the given bag, respecting + * cardinality. + * + * @param coll the collection to retain + * @return true if this call changed the collection + */ + @Override + public boolean retainAll(final Collection coll) { + if (coll instanceof Bag) { + return retainAll((Bag) coll); + } + return retainAll(new HashBag(coll)); + } + + /** + * Remove any members of the bag that are not in the given bag, respecting + * cardinality. + * @see #retainAll(Collection) + * + * @param other the bag to retain + * @return true if this call changed the collection + */ + boolean retainAll(final Bag other) { + boolean result = false; + final Bag excess = new HashBag(); + final Iterator i = uniqueSet().iterator(); + while (i.hasNext()) { + final E current = i.next(); + final int myCount = getCount(current); + final int otherCount = other.getCount(current); + if (1 <= otherCount && otherCount <= myCount) { + excess.add(current, myCount - otherCount); + } else { + excess.add(current, myCount); + } + } + if (!excess.isEmpty()) { + result = removeAll(excess); + } + return result; + } + + //----------------------------------------------------------------------- + /** + * Mutable integer class for storing the data. + */ + protected static class MutableInteger { + /** The value of this mutable. */ + protected int value; + + /** + * Constructor. + * @param value the initial value + */ + MutableInteger(final int value) { + this.value = value; + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableInteger == false) { + return false; + } + return ((MutableInteger) obj).value == value; + } + + @Override + public int hashCode() { + return value; + } + } + + //----------------------------------------------------------------------- + /** + * Returns an array of all of this bag's elements. + * + * @return an array of all of this bag's elements + */ + @Override + public Object[] toArray() { + final Object[] result = new Object[size()]; + int i = 0; + final Iterator it = map.keySet().iterator(); + while (it.hasNext()) { + final E current = it.next(); + for (int index = getCount(current); index > 0; index--) { + result[i++] = current; + } + } + return result; + } + + /** + * Returns an array of all of this bag's elements. + * If the input array has more elements than are in the bag, + * trailing elements will be set to null. + * + * @param the type of the array elements + * @param array the array to populate + * @return an array of all of this bag's elements + * @throws ArrayStoreException if the runtime type of the specified array is not + * a supertype of the runtime type of the elements in this list + * @throws NullPointerException if the specified array is null + */ + @Override + public T[] toArray(T[] array) { + final int size = size(); + if (array.length < size) { + @SuppressWarnings("unchecked") // safe as both are of type T + final T[] unchecked = (T[]) Array.newInstance(array.getClass().getComponentType(), size); + array = unchecked; + } + + int i = 0; + final Iterator it = map.keySet().iterator(); + while (it.hasNext()) { + final E current = it.next(); + for (int index = getCount(current); index > 0; index--) { + // unsafe, will throw ArrayStoreException if types are not compatible, see javadoc + @SuppressWarnings("unchecked") + final T unchecked = (T) current; + array[i++] = unchecked; + } + } + while (i < array.length) { + array[i++] = null; + } + return array; + } + + /** + * Returns an unmodifiable view of the underlying map's key set. + * + * @return the set of unique elements in this bag + */ + @Override + public Set uniqueSet() { + if (uniqueSet == null) { + uniqueSet = UnmodifiableSet. unmodifiableSet(map.keySet()); + } + return uniqueSet; + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * @param out the output stream + * @throws IOException any of the usual I/O related exceptions + */ + protected void doWriteObject(final ObjectOutputStream out) throws IOException { + out.writeInt(map.size()); + for (final Entry entry : map.entrySet()) { + out.writeObject(entry.getKey()); + out.writeInt(entry.getValue().value); + } + } + + /** + * Read the map in using a custom routine. + * @param map the map to use + * @param in the input stream + * @throws IOException any of the usual I/O related exceptions + * @throws ClassNotFoundException if the stream contains an object which class can not be loaded + * @throws ClassCastException if the stream does not contain the correct objects + */ + protected void doReadObject(final Map map, final ObjectInputStream in) + throws IOException, ClassNotFoundException { + this.map = map; + final int entrySize = in.readInt(); + for (int i = 0; i < entrySize; i++) { + @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect + final E obj = (E) in.readObject(); + final int count = in.readInt(); + map.put(obj, new MutableInteger(count)); + size += count; + } + } + + //----------------------------------------------------------------------- + /** + * Compares this Bag to another. This Bag equals another Bag if it contains + * the same number of occurrences of the same elements. + * + * @param object the Bag to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (object instanceof Bag == false) { + return false; + } + final Bag other = (Bag) object; + if (other.size() != size()) { + return false; + } + for (final E element : map.keySet()) { + if (other.getCount(element) != getCount(element)) { + return false; + } + } + return true; + } + + /** + * Gets a hash code for the Bag compatible with the definition of equals. + * The hash code is defined as the sum total of a hash code for each + * element. The per element hash code is defined as + * (e==null ? 0 : e.hashCode()) ^ noOccurances). This hash code + * is compatible with the Set interface. + * + * @return the hash code of the Bag + */ + @Override + public int hashCode() { + int total = 0; + for (final Entry entry : map.entrySet()) { + final E element = entry.getKey(); + final MutableInteger count = entry.getValue(); + total += (element == null ? 0 : element.hashCode()) ^ count.value; + } + return total; + } + + /** + * Implement a toString() method suitable for debugging. + * + * @return a debugging toString + */ + @Override + public String toString() { + if (size() == 0) { + return "[]"; + } + final StringBuilder buf = new StringBuilder(); + buf.append('['); + final Iterator it = uniqueSet().iterator(); + while (it.hasNext()) { + final Object current = it.next(); + final int count = getCount(current); + buf.append(count); + buf.append(':'); + buf.append(current); + if (it.hasNext()) { + buf.append(','); + } + } + buf.append(']'); + return buf.toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractSortedBagDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractSortedBagDecorator.java new file mode 100644 index 000000000..bcd7472bc --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/AbstractSortedBagDecorator.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.util.Comparator; + +import org.apache.commons.collections4.SortedBag; + +/** + * Decorates another SortedBag to provide additional behaviour. + *

          + * Methods are forwarded directly to the decorated bag. + * + * @since 3.0 + * @version $Id: AbstractSortedBagDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractSortedBagDecorator + extends AbstractBagDecorator implements SortedBag { + + /** Serialization version */ + private static final long serialVersionUID = -8223473624050467718L; + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since 3.1 + */ + protected AbstractSortedBagDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param bag the bag to decorate, must not be null + * @throws NullPointerException if bag is null + */ + protected AbstractSortedBagDecorator(final SortedBag bag) { + super(bag); + } + + /** + * Gets the bag being decorated. + * + * @return the decorated bag + */ + @Override + protected SortedBag decorated() { + return (SortedBag) super.decorated(); + } + + //----------------------------------------------------------------------- + + @Override + public E first() { + return decorated().first(); + } + + @Override + public E last() { + return decorated().last(); + } + + @Override + public Comparator comparator() { + return decorated().comparator(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/CollectionBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/CollectionBag.java new file mode 100644 index 000000000..662ecaaa0 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/CollectionBag.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Iterator; + +import org.apache.commons.collections4.Bag; + +/** + * Decorates another {@link Bag} to comply with the Collection contract. + *

          + * By decorating an existing {@link Bag} instance with a {@link CollectionBag}, + * it can be safely passed on to methods that require Collection types that + * are fully compliant with the Collection contract. + *

          + * The method javadoc highlights the differences compared to the original Bag interface. + * + * @see Bag + * @param the type held in the bag + * @since 4.0 + * @version $Id: CollectionBag.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class CollectionBag extends AbstractBagDecorator { + + /** Serialization version */ + private static final long serialVersionUID = -2560033712679053143L; + + /** + * Factory method to create a bag that complies to the Collection contract. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @return a Bag that complies to the Collection contract + * @throws NullPointerException if bag is null + */ + public static Bag collectionBag(final Bag bag) { + return new CollectionBag(bag); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param bag the bag to decorate, must not be null + * @throws NullPointerException if bag is null + */ + public CollectionBag(final Bag bag) { + super(bag); + } + + //----------------------------------------------------------------------- + /** + * Write the collection out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the collection in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @throws ClassCastException if deserialised object has wrong type + */ + @SuppressWarnings("unchecked") // will throw CCE, see Javadoc + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setCollection((Collection) in.readObject()); + } + + //----------------------------------------------------------------------- + // Collection interface + //----------------------------------------------------------------------- + + /** + * (Change) + * Returns true if the bag contains all elements in + * the given collection, not respecting cardinality. That is, + * if the given collection coll contains at least one of + * every object contained in this object. + * + * @param coll the collection to check against + * @return true if the Bag contains at least one of every object in the collection + */ + @Override + public boolean containsAll(final Collection coll) { + final Iterator e = coll.iterator(); + while (e.hasNext()) { + if(!contains(e.next())) { + return false; + } + } + return true; + } + + /** + * (Change) + * Adds one copy of the specified object to the Bag. + *

          + * Since this method always increases the size of the bag, it + * will always return true. + * + * @param object the object to add + * @return true, always + */ + @Override + public boolean add(final E object) { + return add(object, 1); + } + + @Override + public boolean addAll(final Collection coll) { + boolean changed = false; + final Iterator i = coll.iterator(); + while (i.hasNext()) { + final boolean added = add(i.next(), 1); + changed = changed || added; + } + return changed; + } + + /** + * (Change) + * Removes the first occurrence of the given object from the bag. + *

          + * This will also remove the object from the {@link #uniqueSet()} if the + * bag contains no occurrence anymore of the object after this operation. + * + * @param object the object to remove + * @return true if this call changed the collection + */ + @Override + public boolean remove(final Object object) { + return remove(object, 1); + } + + /** + * (Change) + * Remove all elements represented in the given collection, + * not respecting cardinality. That is, remove all + * occurrences of every object contained in the given collection. + * + * @param coll the collection to remove + * @return true if this call changed the collection + */ + @Override + public boolean removeAll(final Collection coll) { + if (coll != null) { + boolean result = false; + final Iterator i = coll.iterator(); + while (i.hasNext()) { + final Object obj = i.next(); + final boolean changed = remove(obj, getCount(obj)); + result = result || changed; + } + return result; + } else { + // let the decorated bag handle the case of null argument + return decorated().removeAll(null); + } + } + + /** + * (Change) + * Remove any members of the bag that are not in the given collection, + * not respecting cardinality. That is, any object in the given + * collection coll will be retained in the bag with the same + * number of copies prior to this operation. All other objects will be + * completely removed from this bag. + *

          + * This implementation iterates over the elements of this bag, checking + * each element in turn to see if it's contained in coll. + * If it's not contained, it's removed from this bag. As a consequence, + * it is advised to use a collection type for coll that provides + * a fast (e.g. O(1)) implementation of {@link Collection#contains(Object)}. + * + * @param coll the collection to retain + * @return true if this call changed the collection + */ + @Override + public boolean retainAll(final Collection coll) { + if (coll != null) { + boolean modified = false; + final Iterator e = iterator(); + while (e.hasNext()) { + if (!coll.contains(e.next())) { + e.remove(); + modified = true; + } + } + return modified; + } else { + // let the decorated bag handle the case of null argument + return decorated().retainAll(null); + } + } + + //----------------------------------------------------------------------- + // Bag interface + //----------------------------------------------------------------------- + + /** + * (Change) + * Adds count copies of the specified object to the Bag. + *

          + * Since this method always increases the size of the bag, it + * will always return true. + * + * @param object the object to add + * @param count the number of copies to add + * @return true, always + */ + @Override + public boolean add(final E object, final int count) { + decorated().add(object, count); + return true; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/CollectionSortedBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/CollectionSortedBag.java new file mode 100644 index 000000000..0af350bc0 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/CollectionSortedBag.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Iterator; + +import org.apache.commons.collections4.SortedBag; + +/** + * Decorates another {@link SortedBag} to comply with the Collection contract. + * + * @since 4.0 + * @version $Id: CollectionSortedBag.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class CollectionSortedBag extends AbstractSortedBagDecorator { + + /** Serialization version */ + private static final long serialVersionUID = -2560033712679053143L; + + /** + * Factory method to create a sorted bag that complies to the Collection contract. + * + * @param the type of the elements in the bag + * @param bag the sorted bag to decorate, must not be null + * @return a SortedBag that complies to the Collection contract + * @throws NullPointerException if bag is null + */ + public static SortedBag collectionSortedBag(final SortedBag bag) { + return new CollectionSortedBag(bag); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param bag the sorted bag to decorate, must not be null + * @throws NullPointerException if bag is null + */ + public CollectionSortedBag(final SortedBag bag) { + super(bag); + } + + //----------------------------------------------------------------------- + /** + * Write the collection out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the collection in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @throws ClassCastException if deserialised object has wrong type + */ + @SuppressWarnings("unchecked") // will throw CCE, see Javadoc + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setCollection((Collection) in.readObject()); + } + + //----------------------------------------------------------------------- + // Collection interface + //----------------------------------------------------------------------- + + @Override + public boolean containsAll(final Collection coll) { + final Iterator e = coll.iterator(); + while (e.hasNext()) { + if(!contains(e.next())) { + return false; + } + } + return true; + } + + @Override + public boolean add(final E object) { + return add(object, 1); + } + + @Override + public boolean addAll(final Collection coll) { + boolean changed = false; + final Iterator i = coll.iterator(); + while (i.hasNext()) { + final boolean added = add(i.next(), 1); + changed = changed || added; + } + return changed; + } + + @Override + public boolean remove(final Object object) { + return remove(object, 1); + } + + @Override + public boolean removeAll(final Collection coll) { + if (coll != null) { + boolean result = false; + final Iterator i = coll.iterator(); + while (i.hasNext()) { + final Object obj = i.next(); + final boolean changed = remove(obj, getCount(obj)); + result = result || changed; + } + return result; + } else { + // let the decorated bag handle the case of null argument + return decorated().removeAll(null); + } + } + + @Override + public boolean retainAll(final Collection coll) { + if (coll != null) { + boolean modified = false; + final Iterator e = iterator(); + while (e.hasNext()) { + if (!coll.contains(e.next())) { + e.remove(); + modified = true; + } + } + return modified; + } else { + // let the decorated bag handle the case of null argument + return decorated().retainAll(null); + } + } + + //----------------------------------------------------------------------- + // Bag interface + //----------------------------------------------------------------------- + + @Override + public boolean add(final E object, final int count) { + decorated().add(object, count); + return true; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/HashBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/HashBag.java new file mode 100644 index 000000000..64f2f3c13 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/HashBag.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; + +/** + * Implements {@code Bag}, using a {@link HashMap} to provide the + * data storage. This is the standard implementation of a bag. + *

          + * A {@code Bag} stores each object in the collection together with a + * count of occurrences. Extra methods on the interface allow multiple copies + * of an object to be added or removed at once. It is important to read the + * interface javadoc carefully as several methods violate the + * {@link Collection} interface specification. + * + * @since 3.0 (previously in main package v2.0) + * @version $Id: HashBag.java 1685902 2015-06-16 20:13:13Z tn $ + */ +public class HashBag extends AbstractMapBag implements Serializable { + + /** Serial version lock */ + private static final long serialVersionUID = -6561115435802554013L; + + /** + * Constructs an empty {@link HashBag}. + */ + public HashBag() { + super(new HashMap()); + } + + /** + * Constructs a bag containing all the members of the given collection. + * + * @param coll a collection to copy into this bag + */ + public HashBag(final Collection coll) { + this(); + addAll(coll); + } + + //----------------------------------------------------------------------- + /** + * Write the bag out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + super.doWriteObject(out); + } + + /** + * Read the bag in using a custom routine. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + super.doReadObject(new HashMap(), in); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/PredicatedBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/PredicatedBag.java new file mode 100644 index 000000000..6a3c51eb8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/PredicatedBag.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.util.Set; + +import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.collection.PredicatedCollection; + +/** + * Decorates another {@link Bag} to validate that additions + * match a specified predicate. + *

          + * This bag exists to provide validation for the decorated bag. + * It is normally created to decorate an empty bag. + * If an object cannot be added to the bag, an {@link IllegalArgumentException} is thrown. + *

          + * One usage would be to ensure that no null entries are added to the bag. + *

          + * Bag bag = PredicatedBag.predicatedBag(new HashBag(), NotNullPredicate.INSTANCE);
          + * 
          + *

          + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: PredicatedBag.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PredicatedBag extends PredicatedCollection implements Bag { + + /** Serialization version */ + private static final long serialVersionUID = -2575833140344736876L; + + /** + * Factory method to create a predicated (validating) bag. + *

          + * If there are any elements already in the bag being decorated, they + * are validated. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @return a new predicated Bag + * @throws NullPointerException if bag or predicate is null + * @throws IllegalArgumentException if the bag contains invalid elements + * @since 4.0 + */ + public static PredicatedBag predicatedBag(final Bag bag, final Predicate predicate) { + return new PredicatedBag(bag, predicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

          + * If there are any elements already in the bag being decorated, they + * are validated. + * + * @param bag the bag to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @throws NullPointerException if bag or predicate is null + * @throws IllegalArgumentException if the bag contains invalid elements + */ + protected PredicatedBag(final Bag bag, final Predicate predicate) { + super(bag, predicate); + } + + /** + * Gets the decorated bag. + * + * @return the decorated bag + */ + @Override + protected Bag decorated() { + return (Bag) super.decorated(); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + //----------------------------------------------------------------------- + + @Override + public boolean add(final E object, final int count) { + validate(object); + return decorated().add(object, count); + } + + @Override + public boolean remove(final Object object, final int count) { + return decorated().remove(object, count); + } + + @Override + public Set uniqueSet() { + return decorated().uniqueSet(); + } + + @Override + public int getCount(final Object object) { + return decorated().getCount(object); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/PredicatedSortedBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/PredicatedSortedBag.java new file mode 100644 index 000000000..6af1881de --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/PredicatedSortedBag.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.util.Comparator; + +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.SortedBag; + +/** + * Decorates another {@link SortedBag} to validate that additions + * match a specified predicate. + *

          + * This bag exists to provide validation for the decorated bag. + * It is normally created to decorate an empty bag. + * If an object cannot be added to the bag, an {@link IllegalArgumentException} is thrown. + *

          + * One usage would be to ensure that no null entries are added to the bag. + *

          + * SortedBag bag = PredicatedSortedBag.predicatedSortedBag(new TreeBag(), NotNullPredicate.INSTANCE);
          + * 
          + *

          + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: PredicatedSortedBag.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PredicatedSortedBag extends PredicatedBag implements SortedBag { + + /** Serialization version */ + private static final long serialVersionUID = 3448581314086406616L; + + /** + * Factory method to create a predicated (validating) bag. + *

          + * If there are any elements already in the bag being decorated, they + * are validated. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @return a new predicated SortedBag + * @throws NullPointerException if bag or predicate is null + * @throws IllegalArgumentException if the bag contains invalid elements + * @since 4.0 + */ + public static PredicatedSortedBag predicatedSortedBag(final SortedBag bag, + final Predicate predicate) { + return new PredicatedSortedBag(bag, predicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

          If there are any elements already in the bag being decorated, they + * are validated. + * + * @param bag the bag to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @throws NullPointerException if bag or predicate is null + * @throws IllegalArgumentException if the bag contains invalid elements + */ + protected PredicatedSortedBag(final SortedBag bag, final Predicate predicate) { + super(bag, predicate); + } + + /** + * Gets the decorated sorted bag. + * + * @return the decorated bag + */ + @Override + protected SortedBag decorated() { + return (SortedBag) super.decorated(); + } + + //----------------------------------------------------------------------- + + @Override + public E first() { + return decorated().first(); + } + + @Override + public E last() { + return decorated().last(); + } + + @Override + public Comparator comparator() { + return decorated().comparator(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/SynchronizedBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/SynchronizedBag.java new file mode 100644 index 000000000..7fb547229 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/SynchronizedBag.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.util.Set; + +import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.collection.SynchronizedCollection; + +/** + * Decorates another {@link Bag} to synchronize its behaviour + * for a multi-threaded environment. + *

          + * Methods are synchronized, then forwarded to the decorated bag. + * Iterators must be separately synchronized around the loop. + *

          + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: SynchronizedBag.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class SynchronizedBag extends SynchronizedCollection implements Bag { + + /** Serialization version */ + private static final long serialVersionUID = 8084674570753837109L; + + /** + * Factory method to create a synchronized bag. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @return a new synchronized Bag + * @throws NullPointerException if bag is null + * @since 4.0 + */ + public static SynchronizedBag synchronizedBag(final Bag bag) { + return new SynchronizedBag(bag); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param bag the bag to decorate, must not be null + * @throws NullPointerException if bag is null + */ + protected SynchronizedBag(final Bag bag) { + super(bag); + } + + /** + * Constructor that wraps (not copies). + * + * @param bag the bag to decorate, must not be null + * @param lock the lock to use, must not be null + * @throws NullPointerException if bag or lock is null + */ + protected SynchronizedBag(final Bag bag, final Object lock) { + super(bag, lock); + } + + /** + * Gets the bag being decorated. + * + * @return the decorated bag + */ + protected Bag getBag() { + return (Bag) decorated(); + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + synchronized (lock) { + return getBag().equals(object); + } + } + + @Override + public int hashCode() { + synchronized (lock) { + return getBag().hashCode(); + } + } + + //----------------------------------------------------------------------- + + @Override + public boolean add(final E object, final int count) { + synchronized (lock) { + return getBag().add(object, count); + } + } + + @Override + public boolean remove(final Object object, final int count) { + synchronized (lock) { + return getBag().remove(object, count); + } + } + + @Override + public Set uniqueSet() { + synchronized (lock) { + final Set set = getBag().uniqueSet(); + return new SynchronizedBagSet(set, lock); + } + } + + @Override + public int getCount(final Object object) { + synchronized (lock) { + return getBag().getCount(object); + } + } + + //----------------------------------------------------------------------- + /** + * Synchronized Set for the Bag class. + */ + class SynchronizedBagSet extends SynchronizedCollection implements Set { + /** Serialization version */ + private static final long serialVersionUID = 2990565892366827855L; + + /** + * Constructor. + * @param set the set to decorate + * @param lock the lock to use, shared with the bag + */ + SynchronizedBagSet(final Set set, final Object lock) { + super(set, lock); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/SynchronizedSortedBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/SynchronizedSortedBag.java new file mode 100644 index 000000000..f945f2896 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/SynchronizedSortedBag.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.util.Comparator; + +import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.SortedBag; + +/** + * Decorates another {@link SortedBag} to synchronize its behaviour + * for a multi-threaded environment. + *

          + * Methods are synchronized, then forwarded to the decorated bag. + * Iterators must be separately synchronized around the loop. + *

          + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: SynchronizedSortedBag.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class SynchronizedSortedBag extends SynchronizedBag implements SortedBag { + + /** Serialization version */ + private static final long serialVersionUID = 722374056718497858L; + + /** + * Factory method to create a synchronized sorted bag. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @return a new synchronized SortedBag + * @throws NullPointerException if bag is null + * @since 4.0 + */ + public static SynchronizedSortedBag synchronizedSortedBag(final SortedBag bag) { + return new SynchronizedSortedBag(bag); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param bag the bag to decorate, must not be null + * @throws NullPointerException if bag is null + */ + protected SynchronizedSortedBag(final SortedBag bag) { + super(bag); + } + + /** + * Constructor that wraps (not copies). + * + * @param bag the bag to decorate, must not be null + * @param lock the lock to use, must not be null + * @throws NullPointerException if bag or lock is null + */ + protected SynchronizedSortedBag(final Bag bag, final Object lock) { + super(bag, lock); + } + + /** + * Gets the bag being decorated. + * + * @return the decorated bag + */ + protected SortedBag getSortedBag() { + return (SortedBag) decorated(); + } + + //----------------------------------------------------------------------- + + @Override + public synchronized E first() { + synchronized (lock) { + return getSortedBag().first(); + } + } + + @Override + public synchronized E last() { + synchronized (lock) { + return getSortedBag().last(); + } + } + + @Override + public synchronized Comparator comparator() { + synchronized (lock) { + return getSortedBag().comparator(); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/TransformedBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/TransformedBag.java new file mode 100644 index 000000000..50825c9ef --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/TransformedBag.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.util.Set; + +import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.collection.TransformedCollection; +import org.apache.commons.collections4.set.TransformedSet; + +/** + * Decorates another {@link Bag} to transform objects that are added. + *

          + * The add methods are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must + * use the Integer form to remove objects. + *

          + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: TransformedBag.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TransformedBag extends TransformedCollection implements Bag { + + /** Serialization version */ + private static final long serialVersionUID = 5421170911299074185L; + + /** + * Factory method to create a transforming bag. + *

          + * If there are any elements already in the bag being decorated, they + * are NOT transformed. Contrast this with {@link #transformedBag(Bag, Transformer)}. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed Bag + * @throws NullPointerException if bag or transformer is null + * @since 4.0 + */ + public static Bag transformingBag(final Bag bag, final Transformer transformer) { + return new TransformedBag(bag, transformer); + } + + /** + * Factory method to create a transforming bag that will transform + * existing contents of the specified bag. + *

          + * If there are any elements already in the bag being decorated, they + * will be transformed by this method. + * Contrast this with {@link #transformingBag(Bag, Transformer)}. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed Bag + * @throws NullPointerException if bag or transformer is null + * @since 4.0 + */ + public static Bag transformedBag(final Bag bag, final Transformer transformer) { + final TransformedBag decorated = new TransformedBag(bag, transformer); + if (bag.size() > 0) { + @SuppressWarnings("unchecked") // Bag is of type E + final E[] values = (E[]) bag.toArray(); // NOPMD - false positive for generics + bag.clear(); + for (final E value : values) { + decorated.decorated().add(transformer.transform(value)); + } + } + return decorated; + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

          + * If there are any elements already in the bag being decorated, they + * are NOT transformed. + * + * @param bag the bag to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @throws NullPointerException if bag or transformer is null + */ + protected TransformedBag(final Bag bag, final Transformer transformer) { + super(bag, transformer); + } + + /** + * Gets the decorated bag. + * + * @return the decorated bag + */ + protected Bag getBag() { + return (Bag) decorated(); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + //----------------------------------------------------------------------- + + @Override + public int getCount(final Object object) { + return getBag().getCount(object); + } + + @Override + public boolean remove(final Object object, final int nCopies) { + return getBag().remove(object, nCopies); + } + + //----------------------------------------------------------------------- + + @Override + public boolean add(final E object, final int nCopies) { + return getBag().add(transform(object), nCopies); + } + + @Override + public Set uniqueSet() { + final Set set = getBag().uniqueSet(); + return TransformedSet.transformingSet(set, transformer); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/TransformedSortedBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/TransformedSortedBag.java new file mode 100644 index 000000000..54557db87 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/TransformedSortedBag.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.util.Comparator; + +import org.apache.commons.collections4.SortedBag; +import org.apache.commons.collections4.Transformer; + +/** + * Decorates another {@link SortedBag} to transform objects that are added. + *

          + * The add methods are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must + * use the Integer form to remove objects. + *

          + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: TransformedSortedBag.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TransformedSortedBag extends TransformedBag implements SortedBag { + + /** Serialization version */ + private static final long serialVersionUID = -251737742649401930L; + + /** + * Factory method to create a transforming sorted bag. + *

          + * If there are any elements already in the bag being decorated, they + * are NOT transformed. Contrast this with {@link #transformedSortedBag(SortedBag, Transformer)}. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed SortedBag + * @throws NullPointerException if bag or transformer is null + * @since 4.0 + */ + public static TransformedSortedBag transformingSortedBag(final SortedBag bag, + final Transformer transformer) { + return new TransformedSortedBag(bag, transformer); + } + + /** + * Factory method to create a transforming sorted bag that will transform + * existing contents of the specified sorted bag. + *

          + * If there are any elements already in the bag being decorated, they + * will be transformed by this method. + * Contrast this with {@link #transformingSortedBag(SortedBag, Transformer)}. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed SortedBag + * @throws NullPointerException if bag or transformer is null + * @since 4.0 + */ + public static TransformedSortedBag transformedSortedBag(final SortedBag bag, + final Transformer transformer) { + + final TransformedSortedBag decorated = new TransformedSortedBag(bag, transformer); + if (bag.size() > 0) { + @SuppressWarnings("unchecked") // bag is type E + final E[] values = (E[]) bag.toArray(); // NOPMD - false positive for generics + bag.clear(); + for (final E value : values) { + decorated.decorated().add(transformer.transform(value)); + } + } + return decorated; + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

          + * If there are any elements already in the bag being decorated, they + * are NOT transformed. + * + * @param bag the bag to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @throws NullPointerException if bag or transformer is null + */ + protected TransformedSortedBag(final SortedBag bag, final Transformer transformer) { + super(bag, transformer); + } + + /** + * Gets the decorated bag. + * + * @return the decorated bag + */ + protected SortedBag getSortedBag() { + return (SortedBag) decorated(); + } + + //----------------------------------------------------------------------- + + @Override + public E first() { + return getSortedBag().first(); + } + + @Override + public E last() { + return getSortedBag().last(); + } + + @Override + public Comparator comparator() { + return getSortedBag().comparator(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/TreeBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/TreeBag.java new file mode 100644 index 000000000..3db2c7fb9 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/TreeBag.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Comparator; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.commons.collections4.SortedBag; + +/** + * Implements {@link SortedBag}, using a {@link TreeMap} to provide the data storage. + * This is the standard implementation of a sorted bag. + *

          + * Order will be maintained among the bag members and can be viewed through the iterator. + *

          + * A {@link org.apache.commons.collections4.Bag Bag} stores each object in the collection + * together with a count of occurrences. Extra methods on the interface allow multiple + * copies of an object to be added or removed at once. It is important to read the interface + * javadoc carefully as several methods violate the {@link Collection} interface specification. + * + * @since 3.0 (previously in main package v2.0) + * @version $Id: TreeBag.java 1683951 2015-06-06 20:19:03Z tn $ + */ +public class TreeBag extends AbstractMapBag implements SortedBag, Serializable { + + /** Serial version lock */ + private static final long serialVersionUID = -7740146511091606676L; + + /** + * Constructs an empty {@link TreeBag}. + */ + public TreeBag() { + super(new TreeMap()); + } + + /** + * Constructs an empty bag that maintains order on its unique representative + * members according to the given {@link Comparator}. + * + * @param comparator the comparator to use + */ + public TreeBag(final Comparator comparator) { + super(new TreeMap(comparator)); + } + + /** + * Constructs a {@link TreeBag} containing all the members of the + * specified collection. + * + * @param coll the collection to copy into the bag + */ + public TreeBag(final Collection coll) { + this(); + addAll(coll); + } + + //----------------------------------------------------------------------- + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the object to be added does not implement + * {@link Comparable} and the {@link TreeBag} is using natural ordering + * @throws NullPointerException if the specified key is null and this bag uses + * natural ordering, or its comparator does not permit null keys + */ + @Override + public boolean add(final E object) { + if(comparator() == null && !(object instanceof Comparable)) { + if (object == null) { + throw new NullPointerException(); + } + throw new IllegalArgumentException("Objects of type " + object.getClass() + " cannot be added to " + + "a naturally ordered TreeBag as it does not implement Comparable"); + } + return super.add(object); + } + + //----------------------------------------------------------------------- + + @Override + public E first() { + return getMap().firstKey(); + } + + @Override + public E last() { + return getMap().lastKey(); + } + + @Override + public Comparator comparator() { + return getMap().comparator(); + } + + @Override + protected SortedMap getMap() { + return (SortedMap) super.getMap(); + } + + //----------------------------------------------------------------------- + /** + * Write the bag out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(comparator()); + super.doWriteObject(out); + } + + /** + * Read the bag in using a custom routine. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect + final Comparator comp = (Comparator) in.readObject(); + super.doReadObject(new TreeMap(comp), in); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/UnmodifiableBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/UnmodifiableBag.java new file mode 100644 index 000000000..cd53fbf05 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/UnmodifiableBag.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another {@link Bag} to ensure it can't be altered. + *

          + * This class is Serializable from Commons Collections 3.1. + *

          + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableBag.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableBag + extends AbstractBagDecorator implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = -1873799975157099624L; + + /** + * Factory method to create an unmodifiable bag. + *

          + * If the bag passed in is already unmodifiable, it is returned. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @return an unmodifiable Bag + * @throws NullPointerException if bag is null + * @since 4.0 + */ + public static Bag unmodifiableBag(final Bag bag) { + if (bag instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final Bag tmpBag = (Bag) bag; + return tmpBag; + } + return new UnmodifiableBag(bag); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param bag the bag to decorate, must not be null + * @throws NullPointerException if bag is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableBag(final Bag bag) { + super((Bag) bag); + } + + //----------------------------------------------------------------------- + /** + * Write the collection out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the collection in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @throws ClassCastException if deserialised object has wrong type + */ + @SuppressWarnings("unchecked") // will throw CCE, see Javadoc + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setCollection((Collection) in.readObject()); + } + + //----------------------------------------------------------------------- + @Override + public Iterator iterator() { + return UnmodifiableIterator. unmodifiableIterator(decorated().iterator()); + } + + @Override + public boolean add(final E object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + @Override + public boolean add(final E object, final int count) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object, final int count) { + throw new UnsupportedOperationException(); + } + + @Override + public Set uniqueSet() { + final Set set = decorated().uniqueSet(); + return UnmodifiableSet. unmodifiableSet(set); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/UnmodifiableSortedBag.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/UnmodifiableSortedBag.java new file mode 100644 index 000000000..f4465cd19 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/UnmodifiableSortedBag.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bag; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.collections4.SortedBag; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another {@link SortedBag} to ensure it can't be altered. + *

          + * This class is Serializable from Commons Collections 3.1. + *

          + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableSortedBag.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableSortedBag + extends AbstractSortedBagDecorator implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = -3190437252665717841L; + + /** + * Factory method to create an unmodifiable bag. + *

          + * If the bag passed in is already unmodifiable, it is returned. + * + * @param the type of the elements in the bag + * @param bag the bag to decorate, must not be null + * @return an unmodifiable SortedBag + * @throws NullPointerException if bag is null + * @since 4.0 + */ + public static SortedBag unmodifiableSortedBag(final SortedBag bag) { + if (bag instanceof Unmodifiable) { + return bag; + } + return new UnmodifiableSortedBag(bag); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param bag the bag to decorate, must not be null + * @throws NullPointerException if bag is null + */ + private UnmodifiableSortedBag(final SortedBag bag) { + super(bag); + } + + //----------------------------------------------------------------------- + /** + * Write the collection out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the collection in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @throws ClassCastException if deserialised object has wrong type + */ + @SuppressWarnings("unchecked") // will throw CCE, see Javadoc + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setCollection((Collection) in.readObject()); + } + + //----------------------------------------------------------------------- + @Override + public Iterator iterator() { + return UnmodifiableIterator.unmodifiableIterator(decorated().iterator()); + } + + @Override + public boolean add(final E object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + @Override + public boolean add(final E object, final int count) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object, final int count) { + throw new UnsupportedOperationException(); + } + + @Override + public Set uniqueSet() { + final Set set = decorated().uniqueSet(); + return UnmodifiableSet.unmodifiableSet(set); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bag/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/bag/package-info.java new file mode 100644 index 000000000..a8da5ca9a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bag/package-info.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the {@link org.apache.commons.collections4.Bag Bag} and + * {@link org.apache.commons.collections4.SortedBag SortedBag} interfaces. + * A bag stores an object and a count of the number of occurrences of the object. + *

          + * The following implementations are provided in the package: + *

            + *
          • HashBag - implementation that uses a HashMap to store the data + *
          • TreeBag - implementation that uses a TreeMap to store the data + *
          + *

          + * The following decorators are provided in the package: + *

            + *
          • Synchronized - synchronizes method access for multi-threaded environments + *
          • Unmodifiable - ensures the bag cannot be altered + *
          • Predicated - ensures that only elements that are valid according to a predicate can be added + *
          • Transformed - transforms each element added to the bag + *
          • Collection - ensures compliance with the java.util.Collection contract + *
          + * + * @version $Id: package-info.java 1540804 2013-11-11 18:58:31Z tn $ + */ +package org.apache.commons.collections4.bag; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractBidiMapDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractBidiMapDecorator.java new file mode 100644 index 000000000..f470916cd --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractBidiMapDecorator.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import java.util.Set; + +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.map.AbstractMapDecorator; + +/** + * Provides a base decorator that enables additional functionality to be added + * to a BidiMap via decoration. + *

          + * Methods are forwarded directly to the decorated map. + *

          + * This implementation does not perform any special processing with the map views. + * Instead it simply returns the set/collection from the wrapped map. This may be + * undesirable, for example if you are trying to write a validating implementation + * it would provide a loophole around the validation. + * But, you might want that loophole, so this class is kept simple. + * + * @since 3.0 + * @version $Id: AbstractBidiMapDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractBidiMapDecorator + extends AbstractMapDecorator implements BidiMap { + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if the collection is null + */ + protected AbstractBidiMapDecorator(final BidiMap map) { + super(map); + } + + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + @Override + protected BidiMap decorated() { + return (BidiMap) super.decorated(); + } + + //----------------------------------------------------------------------- + @Override + public MapIterator mapIterator() { + return decorated().mapIterator(); + } + + @Override + public K getKey(final Object value) { + return decorated().getKey(value); + } + + @Override + public K removeValue(final Object value) { + return decorated().removeValue(value); + } + + @Override + public BidiMap inverseBidiMap() { + return decorated().inverseBidiMap(); + } + + @Override + public Set values() { + return decorated().values(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractDualBidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractDualBidiMap.java new file mode 100644 index 000000000..7c24f5e38 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractDualBidiMap.java @@ -0,0 +1,805 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.ResettableIterator; +import org.apache.commons.collections4.collection.AbstractCollectionDecorator; +import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; +import org.apache.commons.collections4.keyvalue.AbstractMapEntryDecorator; + +/** + * Abstract {@link BidiMap} implemented using two maps. + *

          + * An implementation can be written simply by implementing the + * {@link #createBidiMap(Map, Map, BidiMap)} method. + * + * @see DualHashBidiMap + * @see DualTreeBidiMap + * @since 3.0 + * @version $Id: AbstractDualBidiMap.java 1683951 2015-06-06 20:19:03Z tn $ + */ +public abstract class AbstractDualBidiMap implements BidiMap { + + /** + * Normal delegate map. + */ + transient Map normalMap; + + /** + * Reverse delegate map. + */ + transient Map reverseMap; + + /** + * Inverse view of this map. + */ + transient BidiMap inverseBidiMap = null; + + /** + * View of the keys. + */ + transient Set keySet = null; + + /** + * View of the values. + */ + transient Set values = null; + + /** + * View of the entries. + */ + transient Set> entrySet = null; + + /** + * Creates an empty map, initialised by createMap. + *

          + * This constructor remains in place for deserialization. + * All other usage is deprecated in favour of + * {@link #AbstractDualBidiMap(Map, Map)}. + */ + protected AbstractDualBidiMap() { + super(); + } + + /** + * Creates an empty map using the two maps specified as storage. + *

          + * The two maps must be a matching pair, normal and reverse. + * They will typically both be empty. + *

          + * Neither map is validated, so nulls may be passed in. + * If you choose to do this then the subclass constructor must populate + * the maps[] instance variable itself. + * + * @param normalMap the normal direction map + * @param reverseMap the reverse direction map + * @since 3.1 + */ + protected AbstractDualBidiMap(final Map normalMap, final Map reverseMap) { + super(); + this.normalMap = normalMap; + this.reverseMap = reverseMap; + } + + /** + * Constructs a map that decorates the specified maps, + * used by the subclass createBidiMap implementation. + * + * @param normalMap the normal direction map + * @param reverseMap the reverse direction map + * @param inverseBidiMap the inverse BidiMap + */ + protected AbstractDualBidiMap(final Map normalMap, final Map reverseMap, + final BidiMap inverseBidiMap) { + super(); + this.normalMap = normalMap; + this.reverseMap = reverseMap; + this.inverseBidiMap = inverseBidiMap; + } + + /** + * Creates a new instance of the subclass. + * + * @param normalMap the normal direction map + * @param reverseMap the reverse direction map + * @param inverseMap this map, which is the inverse in the new map + * @return the inverse map + */ + protected abstract BidiMap createBidiMap(Map normalMap, Map reverseMap, BidiMap inverseMap); + + // Map delegation + //----------------------------------------------------------------------- + + @Override + public V get(final Object key) { + return normalMap.get(key); + } + + @Override + public int size() { + return normalMap.size(); + } + + @Override + public boolean isEmpty() { + return normalMap.isEmpty(); + } + + @Override + public boolean containsKey(final Object key) { + return normalMap.containsKey(key); + } + + @Override + public boolean equals(final Object obj) { + return normalMap.equals(obj); + } + + @Override + public int hashCode() { + return normalMap.hashCode(); + } + + @Override + public String toString() { + return normalMap.toString(); + } + + // BidiMap changes + //----------------------------------------------------------------------- + + @Override + public V put(final K key, final V value) { + if (normalMap.containsKey(key)) { + reverseMap.remove(normalMap.get(key)); + } + if (reverseMap.containsKey(value)) { + normalMap.remove(reverseMap.get(value)); + } + final V obj = normalMap.put(key, value); + reverseMap.put(value, key); + return obj; + } + + @Override + public void putAll(final Map map) { + for (final Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public V remove(final Object key) { + V value = null; + if (normalMap.containsKey(key)) { + value = normalMap.remove(key); + reverseMap.remove(value); + } + return value; + } + + @Override + public void clear() { + normalMap.clear(); + reverseMap.clear(); + } + + @Override + public boolean containsValue(final Object value) { + return reverseMap.containsKey(value); + } + + // BidiMap + //----------------------------------------------------------------------- + /** + * Obtains a MapIterator over the map. + * The iterator implements ResetableMapIterator. + * This implementation relies on the entrySet iterator. + *

          + * The setValue() methods only allow a new value to be set. + * If the value being set is already in the map, an IllegalArgumentException + * is thrown (as setValue cannot change the size of the map). + * + * @return a map iterator + */ + @Override + public MapIterator mapIterator() { + return new BidiMapIterator(this); + } + + @Override + public K getKey(final Object value) { + return reverseMap.get(value); + } + + @Override + public K removeValue(final Object value) { + K key = null; + if (reverseMap.containsKey(value)) { + key = reverseMap.remove(value); + normalMap.remove(key); + } + return key; + } + + @Override + public BidiMap inverseBidiMap() { + if (inverseBidiMap == null) { + inverseBidiMap = createBidiMap(reverseMap, normalMap, this); + } + return inverseBidiMap; + } + + // Map views + //----------------------------------------------------------------------- + /** + * Gets a keySet view of the map. + * Changes made on the view are reflected in the map. + * The set supports remove and clear but not add. + * + * @return the keySet view + */ + @Override + public Set keySet() { + if (keySet == null) { + keySet = new KeySet(this); + } + return keySet; + } + + /** + * Creates a key set iterator. + * Subclasses can override this to return iterators with different properties. + * + * @param iterator the iterator to decorate + * @return the keySet iterator + */ + protected Iterator createKeySetIterator(final Iterator iterator) { + return new KeySetIterator(iterator, this); + } + + /** + * Gets a values view of the map. + * Changes made on the view are reflected in the map. + * The set supports remove and clear but not add. + * + * @return the values view + */ + @Override + public Set values() { + if (values == null) { + values = new Values(this); + } + return values; + } + + /** + * Creates a values iterator. + * Subclasses can override this to return iterators with different properties. + * + * @param iterator the iterator to decorate + * @return the values iterator + */ + protected Iterator createValuesIterator(final Iterator iterator) { + return new ValuesIterator(iterator, this); + } + + /** + * Gets an entrySet view of the map. + * Changes made on the set are reflected in the map. + * The set supports remove and clear but not add. + *

          + * The Map Entry setValue() method only allow a new value to be set. + * If the value being set is already in the map, an IllegalArgumentException + * is thrown (as setValue cannot change the size of the map). + * + * @return the entrySet view + */ + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = new EntrySet(this); + } + return entrySet; + } + + /** + * Creates an entry set iterator. + * Subclasses can override this to return iterators with different properties. + * + * @param iterator the iterator to decorate + * @return the entrySet iterator + */ + protected Iterator> createEntrySetIterator(final Iterator> iterator) { + return new EntrySetIterator(iterator, this); + } + + //----------------------------------------------------------------------- + /** + * Inner class View. + */ + protected static abstract class View extends AbstractCollectionDecorator { + + /** Generated serial version ID. */ + private static final long serialVersionUID = 4621510560119690639L; + + /** The parent map */ + protected final AbstractDualBidiMap parent; + + /** + * Constructs a new view of the BidiMap. + * + * @param coll the collection view being decorated + * @param parent the parent BidiMap + */ + protected View(final Collection coll, final AbstractDualBidiMap parent) { + super(coll); + this.parent = parent; + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + @Override + public boolean removeAll(final Collection coll) { + if (parent.isEmpty() || coll.isEmpty()) { + return false; + } + boolean modified = false; + final Iterator it = coll.iterator(); + while (it.hasNext()) { + modified |= remove(it.next()); + } + return modified; + } + + /** + * {@inheritDoc} + *

          + * This implementation iterates over the elements of this bidi map, checking each element in + * turn to see if it's contained in coll. If it's not contained, it's removed + * from this bidi map. As a consequence, it is advised to use a collection type for + * coll that provides a fast (e.g. O(1)) implementation of + * {@link Collection#contains(Object)}. + */ + @Override + public boolean retainAll(final Collection coll) { + if (parent.isEmpty()) { + return false; + } + if (coll.isEmpty()) { + parent.clear(); + return true; + } + boolean modified = false; + final Iterator it = iterator(); + while (it.hasNext()) { + if (coll.contains(it.next()) == false) { + it.remove(); + modified = true; + } + } + return modified; + } + + @Override + public void clear() { + parent.clear(); + } + } + + //----------------------------------------------------------------------- + /** + * Inner class KeySet. + */ + protected static class KeySet extends View implements Set { + + /** Serialization version */ + private static final long serialVersionUID = -7107935777385040694L; + + /** + * Constructs a new view of the BidiMap. + * + * @param parent the parent BidiMap + */ + @SuppressWarnings("unchecked") + protected KeySet(final AbstractDualBidiMap parent) { + super(parent.normalMap.keySet(), (AbstractDualBidiMap) parent); + } + + @Override + public Iterator iterator() { + return parent.createKeySetIterator(super.iterator()); + } + + @Override + public boolean contains(final Object key) { + return parent.normalMap.containsKey(key); + } + + @Override + public boolean remove(final Object key) { + if (parent.normalMap.containsKey(key)) { + final Object value = parent.normalMap.remove(key); + parent.reverseMap.remove(value); + return true; + } + return false; + } + } + + /** + * Inner class KeySetIterator. + */ + protected static class KeySetIterator extends AbstractIteratorDecorator { + + /** The parent map */ + protected final AbstractDualBidiMap parent; + + /** The last returned key */ + protected K lastKey = null; + + /** Whether remove is allowed at present */ + protected boolean canRemove = false; + + /** + * Constructor. + * @param iterator the iterator to decorate + * @param parent the parent map + */ + protected KeySetIterator(final Iterator iterator, final AbstractDualBidiMap parent) { + super(iterator); + this.parent = parent; + } + + @Override + public K next() { + lastKey = super.next(); + canRemove = true; + return lastKey; + } + + @Override + public void remove() { + if (canRemove == false) { + throw new IllegalStateException("Iterator remove() can only be called once after next()"); + } + final Object value = parent.normalMap.get(lastKey); + super.remove(); + parent.reverseMap.remove(value); + lastKey = null; + canRemove = false; + } + } + + //----------------------------------------------------------------------- + /** + * Inner class Values. + */ + protected static class Values extends View implements Set { + + /** Serialization version */ + private static final long serialVersionUID = 4023777119829639864L; + + /** + * Constructs a new view of the BidiMap. + * + * @param parent the parent BidiMap + */ + @SuppressWarnings("unchecked") + protected Values(final AbstractDualBidiMap parent) { + super(parent.normalMap.values(), (AbstractDualBidiMap) parent); + } + + @Override + public Iterator iterator() { + return parent.createValuesIterator(super.iterator()); + } + + @Override + public boolean contains(final Object value) { + return parent.reverseMap.containsKey(value); + } + + @Override + public boolean remove(final Object value) { + if (parent.reverseMap.containsKey(value)) { + final Object key = parent.reverseMap.remove(value); + parent.normalMap.remove(key); + return true; + } + return false; + } + } + + /** + * Inner class ValuesIterator. + */ + protected static class ValuesIterator extends AbstractIteratorDecorator { + + /** The parent map */ + protected final AbstractDualBidiMap parent; + + /** The last returned value */ + protected V lastValue = null; + + /** Whether remove is allowed at present */ + protected boolean canRemove = false; + + /** + * Constructor. + * @param iterator the iterator to decorate + * @param parent the parent map + */ + @SuppressWarnings("unchecked") + protected ValuesIterator(final Iterator iterator, final AbstractDualBidiMap parent) { + super(iterator); + this.parent = (AbstractDualBidiMap) parent; + } + + @Override + public V next() { + lastValue = super.next(); + canRemove = true; + return lastValue; + } + + @Override + public void remove() { + if (canRemove == false) { + throw new IllegalStateException("Iterator remove() can only be called once after next()"); + } + super.remove(); // removes from maps[0] + parent.reverseMap.remove(lastValue); + lastValue = null; + canRemove = false; + } + } + + //----------------------------------------------------------------------- + /** + * Inner class EntrySet. + */ + protected static class EntrySet extends View> implements Set> { + + /** Serialization version */ + private static final long serialVersionUID = 4040410962603292348L; + + /** + * Constructs a new view of the BidiMap. + * + * @param parent the parent BidiMap + */ + protected EntrySet(final AbstractDualBidiMap parent) { + super(parent.normalMap.entrySet(), parent); + } + + @Override + public Iterator> iterator() { + return parent.createEntrySetIterator(super.iterator()); + } + + @Override + public boolean remove(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry entry = (Map.Entry) obj; + final Object key = entry.getKey(); + if (parent.containsKey(key)) { + final V value = parent.normalMap.get(key); + if (value == null ? entry.getValue() == null : value.equals(entry.getValue())) { + parent.normalMap.remove(key); + parent.reverseMap.remove(value); + return true; + } + } + return false; + } + } + + /** + * Inner class EntrySetIterator. + */ + protected static class EntrySetIterator extends AbstractIteratorDecorator> { + + /** The parent map */ + protected final AbstractDualBidiMap parent; + + /** The last returned entry */ + protected Map.Entry last = null; + + /** Whether remove is allowed at present */ + protected boolean canRemove = false; + + /** + * Constructor. + * @param iterator the iterator to decorate + * @param parent the parent map + */ + protected EntrySetIterator(final Iterator> iterator, final AbstractDualBidiMap parent) { + super(iterator); + this.parent = parent; + } + + @Override + public Map.Entry next() { + last = new MapEntry(super.next(), parent); + canRemove = true; + return last; + } + + @Override + public void remove() { + if (canRemove == false) { + throw new IllegalStateException("Iterator remove() can only be called once after next()"); + } + // store value as remove may change the entry in the decorator (eg.TreeMap) + final Object value = last.getValue(); + super.remove(); + parent.reverseMap.remove(value); + last = null; + canRemove = false; + } + } + + /** + * Inner class MapEntry. + */ + protected static class MapEntry extends AbstractMapEntryDecorator { + + /** The parent map */ + protected final AbstractDualBidiMap parent; + + /** + * Constructor. + * @param entry the entry to decorate + * @param parent the parent map + */ + protected MapEntry(final Map.Entry entry, final AbstractDualBidiMap parent) { + super(entry); + this.parent = parent; + } + + @Override + public V setValue(final V value) { + final K key = MapEntry.this.getKey(); + if (parent.reverseMap.containsKey(value) && + parent.reverseMap.get(value) != key) { + throw new IllegalArgumentException( + "Cannot use setValue() when the object being set is already in the map"); + } + parent.put(key, value); + return super.setValue(value); + } + } + + /** + * Inner class MapIterator. + */ + protected static class BidiMapIterator implements MapIterator, ResettableIterator { + + /** The parent map */ + protected final AbstractDualBidiMap parent; + + /** The iterator being wrapped */ + protected Iterator> iterator; + + /** The last returned entry */ + protected Map.Entry last = null; + + /** Whether remove is allowed at present */ + protected boolean canRemove = false; + + /** + * Constructor. + * @param parent the parent map + */ + protected BidiMapIterator(final AbstractDualBidiMap parent) { + super(); + this.parent = parent; + this.iterator = parent.normalMap.entrySet().iterator(); + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public K next() { + last = iterator.next(); + canRemove = true; + return last.getKey(); + } + + @Override + public void remove() { + if (canRemove == false) { + throw new IllegalStateException("Iterator remove() can only be called once after next()"); + } + // store value as remove may change the entry in the decorator (eg.TreeMap) + final V value = last.getValue(); + iterator.remove(); + parent.reverseMap.remove(value); + last = null; + canRemove = false; + } + + @Override + public K getKey() { + if (last == null) { + throw new IllegalStateException( + "Iterator getKey() can only be called after next() and before remove()"); + } + return last.getKey(); + } + + @Override + public V getValue() { + if (last == null) { + throw new IllegalStateException( + "Iterator getValue() can only be called after next() and before remove()"); + } + return last.getValue(); + } + + @Override + public V setValue(final V value) { + if (last == null) { + throw new IllegalStateException( + "Iterator setValue() can only be called after next() and before remove()"); + } + if (parent.reverseMap.containsKey(value) && + parent.reverseMap.get(value) != last.getKey()) { + throw new IllegalArgumentException( + "Cannot use setValue() when the object being set is already in the map"); + } + return parent.put(last.getKey(), value); + } + + @Override + public void reset() { + iterator = parent.normalMap.entrySet().iterator(); + last = null; + canRemove = false; + } + + @Override + public String toString() { + if (last != null) { + return "MapIterator[" + getKey() + "=" + getValue() + "]"; + } + return "MapIterator[]"; + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractOrderedBidiMapDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractOrderedBidiMapDecorator.java new file mode 100644 index 000000000..ef7823fa4 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractOrderedBidiMapDecorator.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import org.apache.commons.collections4.OrderedBidiMap; +import org.apache.commons.collections4.OrderedMapIterator; + +/** + * Provides a base decorator that enables additional functionality to be added + * to an OrderedBidiMap via decoration. + *

          + * Methods are forwarded directly to the decorated map. + *

          + * This implementation does not perform any special processing with the map views. + * Instead it simply returns the inverse from the wrapped map. This may be + * undesirable, for example if you are trying to write a validating implementation + * it would provide a loophole around the validation. + * But, you might want that loophole, so this class is kept simple. + * + * @since 3.0 + * @version $Id: AbstractOrderedBidiMapDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractOrderedBidiMapDecorator + extends AbstractBidiMapDecorator + implements OrderedBidiMap { + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if the collection is null + */ + protected AbstractOrderedBidiMapDecorator(final OrderedBidiMap map) { + super(map); + } + + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + @Override + protected OrderedBidiMap decorated() { + return (OrderedBidiMap) super.decorated(); + } + + //----------------------------------------------------------------------- + @Override + public OrderedMapIterator mapIterator() { + return decorated().mapIterator(); + } + + @Override + public K firstKey() { + return decorated().firstKey(); + } + + @Override + public K lastKey() { + return decorated().lastKey(); + } + + @Override + public K nextKey(final K key) { + return decorated().nextKey(key); + } + + @Override + public K previousKey(final K key) { + return decorated().previousKey(key); + } + + @Override + public OrderedBidiMap inverseBidiMap() { + return decorated().inverseBidiMap(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractSortedBidiMapDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractSortedBidiMapDecorator.java new file mode 100644 index 000000000..f4195df69 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/AbstractSortedBidiMapDecorator.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import java.util.Comparator; +import java.util.SortedMap; + +import org.apache.commons.collections4.SortedBidiMap; + +/** + * Provides a base decorator that enables additional functionality to be added + * to a SortedBidiMap via decoration. + *

          + * Methods are forwarded directly to the decorated map. + *

          + * This implementation does not perform any special processing with the map views. + * Instead it simply returns the inverse from the wrapped map. This may be + * undesirable, for example if you are trying to write a validating implementation + * it would provide a loophole around the validation. + * But, you might want that loophole, so this class is kept simple. + * + * @since 3.0 + * @version $Id: AbstractSortedBidiMapDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractSortedBidiMapDecorator + extends AbstractOrderedBidiMapDecorator implements SortedBidiMap { + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if the collection is null + */ + public AbstractSortedBidiMapDecorator(final SortedBidiMap map) { + super(map); + } + + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + @Override + protected SortedBidiMap decorated() { + return (SortedBidiMap) super.decorated(); + } + + //----------------------------------------------------------------------- + @Override + public SortedBidiMap inverseBidiMap() { + return decorated().inverseBidiMap(); + } + + @Override + public Comparator comparator() { + return decorated().comparator(); + } + + @Override + public Comparator valueComparator() { + return decorated().valueComparator(); + } + + @Override + public SortedMap subMap(final K fromKey, final K toKey) { + return decorated().subMap(fromKey, toKey); + } + + @Override + public SortedMap headMap(final K toKey) { + return decorated().headMap(toKey); + } + + @Override + public SortedMap tailMap(final K fromKey) { + return decorated().tailMap(fromKey); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualHashBidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualHashBidiMap.java new file mode 100644 index 000000000..3187ccda8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualHashBidiMap.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.collections4.BidiMap; + +/** + * Implementation of {@link BidiMap} that uses two {@link HashMap} instances. + *

          + * Two {@link HashMap} instances are used in this class. + * This provides fast lookups at the expense of storing two sets of map entries. + * Commons Collections would welcome the addition of a direct hash-based + * implementation of the {@link BidiMap} interface. + *

          + * NOTE: From Commons Collections 3.1, all subclasses will use {@link HashMap} + * and the flawed createMap method is ignored. + * + * @since 3.0 + * @version $Id: DualHashBidiMap.java 1533984 2013-10-20 21:12:51Z tn $ + */ +public class DualHashBidiMap extends AbstractDualBidiMap implements Serializable { + + /** Ensure serialization compatibility */ + private static final long serialVersionUID = 721969328361808L; + + /** + * Creates an empty HashBidiMap. + */ + public DualHashBidiMap() { + super(new HashMap(), new HashMap()); + } + + /** + * Constructs a HashBidiMap and copies the mappings from + * specified Map. + * + * @param map the map whose mappings are to be placed in this map + */ + public DualHashBidiMap(final Map map) { + super(new HashMap(), new HashMap()); + putAll(map); + } + + /** + * Constructs a HashBidiMap that decorates the specified maps. + * + * @param normalMap the normal direction map + * @param reverseMap the reverse direction map + * @param inverseBidiMap the inverse BidiMap + */ + protected DualHashBidiMap(final Map normalMap, final Map reverseMap, + final BidiMap inverseBidiMap) { + super(normalMap, reverseMap, inverseBidiMap); + } + + /** + * Creates a new instance of this object. + * + * @param normalMap the normal direction map + * @param reverseMap the reverse direction map + * @param inverseBidiMap the inverse BidiMap + * @return new bidi map + */ + @Override + protected BidiMap createBidiMap(final Map normalMap, final Map reverseMap, + final BidiMap inverseBidiMap) { + return new DualHashBidiMap(normalMap, reverseMap, inverseBidiMap); + } + + // Serialization + //----------------------------------------------------------------------- + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(normalMap); + } + + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + normalMap = new HashMap(); + reverseMap = new HashMap(); + @SuppressWarnings("unchecked") // will fail at runtime if stream is incorrect + final Map map = (Map) in.readObject(); + putAll(map); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualLinkedHashBidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualLinkedHashBidiMap.java new file mode 100644 index 000000000..0b77fbd20 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualLinkedHashBidiMap.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.collections4.BidiMap; + +/** + * Implementation of BidiMap that uses two LinkedHashMap instances. + *

          + * Two LinkedHashMap instances are used in this class. + * This provides fast lookups at the expense of storing two sets of map entries and two linked lists. + * + * @version $Id: DualLinkedHashBidiMap.java 1533984 2013-10-20 21:12:51Z tn $ + * @since 4.0 + */ +public class DualLinkedHashBidiMap extends AbstractDualBidiMap implements Serializable { + + /** Ensure serialization compatibility */ + private static final long serialVersionUID = 721969328361810L; + + /** + * Creates an empty HashBidiMap. + */ + public DualLinkedHashBidiMap() { + super(new LinkedHashMap(), new LinkedHashMap()); + } + + /** + * Constructs a LinkedHashBidiMap and copies the mappings from + * specified Map. + * + * @param map the map whose mappings are to be placed in this map + */ + public DualLinkedHashBidiMap(final Map map) { + super(new LinkedHashMap(), new LinkedHashMap()); + putAll(map); + } + + /** + * Constructs a LinkedHashBidiMap that decorates the specified maps. + * + * @param normalMap the normal direction map + * @param reverseMap the reverse direction map + * @param inverseBidiMap the inverse BidiMap + */ + protected DualLinkedHashBidiMap(final Map normalMap, final Map reverseMap, + final BidiMap inverseBidiMap) { + super(normalMap, reverseMap, inverseBidiMap); + } + + /** + * Creates a new instance of this object. + * + * @param normalMap the normal direction map + * @param reverseMap the reverse direction map + * @param inverseBidiMap the inverse BidiMap + * @return new bidi map + */ + @Override + protected BidiMap createBidiMap(final Map normalMap, final Map reverseMap, + final BidiMap inverseBidiMap) { + return new DualLinkedHashBidiMap(normalMap, reverseMap, inverseBidiMap); + } + + // Serialization + //----------------------------------------------------------------------- + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(normalMap); + } + + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + normalMap = new LinkedHashMap(); + reverseMap = new LinkedHashMap(); + @SuppressWarnings("unchecked") // will fail at runtime if stream is incorrect + final Map map = (Map) in.readObject(); + putAll(map); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualTreeBidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualTreeBidiMap.java new file mode 100644 index 000000000..f1872c15f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/DualTreeBidiMap.java @@ -0,0 +1,416 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.OrderedBidiMap; +import org.apache.commons.collections4.OrderedMap; +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.ResettableIterator; +import org.apache.commons.collections4.SortedBidiMap; +import org.apache.commons.collections4.map.AbstractSortedMapDecorator; + +/** + * Implementation of {@link BidiMap} that uses two {@link TreeMap} instances. + *

          + * The setValue() method on iterators will succeed only if the new value being set is + * not already in the bidimap. + *

          + * When considering whether to use this class, the {@link TreeBidiMap} class should + * also be considered. It implements the interface using a dedicated design, and does + * not store each object twice, which can save on memory use. + *

          + * NOTE: From Commons Collections 3.1, all subclasses will use {@link TreeMap} + * and the flawed createMap method is ignored. + * + * @since 3.0 + * @version $Id: DualTreeBidiMap.java 1683951 2015-06-06 20:19:03Z tn $ + */ +public class DualTreeBidiMap extends AbstractDualBidiMap + implements SortedBidiMap, Serializable { + + /** Ensure serialization compatibility */ + private static final long serialVersionUID = 721969328361809L; + + /** The key comparator to use */ + private final Comparator comparator; + + /** The value comparator to use */ + private final Comparator valueComparator; + + /** + * Creates an empty DualTreeBidiMap + */ + public DualTreeBidiMap() { + super(new TreeMap(), new TreeMap()); + this.comparator = null; + this.valueComparator = null; + } + + /** + * Constructs a DualTreeBidiMap and copies the mappings from + * specified Map. + * + * @param map the map whose mappings are to be placed in this map + */ + public DualTreeBidiMap(final Map map) { + super(new TreeMap(), new TreeMap()); + putAll(map); + this.comparator = null; + this.valueComparator = null; + } + + /** + * Constructs a {@link DualTreeBidiMap} using the specified {@link Comparator}. + * + * @param keyComparator the comparator + * @param valueComparator the values comparator to use + */ + public DualTreeBidiMap(final Comparator keyComparator, final Comparator valueComparator) { + super(new TreeMap(keyComparator), new TreeMap(valueComparator)); + this.comparator = keyComparator; + this.valueComparator = valueComparator; + } + + /** + * Constructs a {@link DualTreeBidiMap} that decorates the specified maps. + * + * @param normalMap the normal direction map + * @param reverseMap the reverse direction map + * @param inverseBidiMap the inverse BidiMap + */ + protected DualTreeBidiMap(final Map normalMap, final Map reverseMap, + final BidiMap inverseBidiMap) { + super(normalMap, reverseMap, inverseBidiMap); + this.comparator = ((SortedMap) normalMap).comparator(); + this.valueComparator = ((SortedMap) reverseMap).comparator(); + } + + /** + * Creates a new instance of this object. + * + * @param normalMap the normal direction map + * @param reverseMap the reverse direction map + * @param inverseMap the inverse BidiMap + * @return new bidi map + */ + @Override + protected DualTreeBidiMap createBidiMap(final Map normalMap, final Map reverseMap, + final BidiMap inverseMap) { + return new DualTreeBidiMap(normalMap, reverseMap, inverseMap); + } + + //----------------------------------------------------------------------- + + @Override + public Comparator comparator() { + return ((SortedMap) normalMap).comparator(); + } + + @Override + public Comparator valueComparator() { + return ((SortedMap) reverseMap).comparator(); + } + + @Override + public K firstKey() { + return ((SortedMap) normalMap).firstKey(); + } + + @Override + public K lastKey() { + return ((SortedMap) normalMap).lastKey(); + } + + @Override + public K nextKey(final K key) { + if (isEmpty()) { + return null; + } + if (normalMap instanceof OrderedMap) { + return ((OrderedMap) normalMap).nextKey(key); + } + final SortedMap sm = (SortedMap) normalMap; + final Iterator it = sm.tailMap(key).keySet().iterator(); + it.next(); + if (it.hasNext()) { + return it.next(); + } + return null; + } + + @Override + public K previousKey(final K key) { + if (isEmpty()) { + return null; + } + if (normalMap instanceof OrderedMap) { + return ((OrderedMap) normalMap).previousKey(key); + } + final SortedMap sm = (SortedMap) normalMap; + final SortedMap hm = sm.headMap(key); + if (hm.isEmpty()) { + return null; + } + return hm.lastKey(); + } + + //----------------------------------------------------------------------- + /** + * Obtains an ordered map iterator. + *

          + * This implementation copies the elements to an ArrayList in order to + * provide the forward/backward behaviour. + * + * @return a new ordered map iterator + */ + @Override + public OrderedMapIterator mapIterator() { + return new BidiOrderedMapIterator(this); + } + + public SortedBidiMap inverseSortedBidiMap() { + return inverseBidiMap(); + } + + public OrderedBidiMap inverseOrderedBidiMap() { + return inverseBidiMap(); + } + + //----------------------------------------------------------------------- + + @Override + public SortedMap headMap(final K toKey) { + final SortedMap sub = ((SortedMap) normalMap).headMap(toKey); + return new ViewMap(this, sub); + } + + @Override + public SortedMap tailMap(final K fromKey) { + final SortedMap sub = ((SortedMap) normalMap).tailMap(fromKey); + return new ViewMap(this, sub); + } + + @Override + public SortedMap subMap(final K fromKey, final K toKey) { + final SortedMap sub = ((SortedMap) normalMap).subMap(fromKey, toKey); + return new ViewMap(this, sub); + } + + @Override + public SortedBidiMap inverseBidiMap() { + return (SortedBidiMap) super.inverseBidiMap(); + } + + //----------------------------------------------------------------------- + /** + * Internal sorted map view. + */ + protected static class ViewMap extends AbstractSortedMapDecorator { + /** + * Constructor. + * @param bidi the parent bidi map + * @param sm the subMap sorted map + */ + protected ViewMap(final DualTreeBidiMap bidi, final SortedMap sm) { + // the implementation is not great here... + // use the normalMap as the filtered map, but reverseMap as the full map + // this forces containsValue and clear to be overridden + super(new DualTreeBidiMap(sm, bidi.reverseMap, bidi.inverseBidiMap)); + } + + @Override + public boolean containsValue(final Object value) { + // override as default implementation uses reverseMap + return decorated().normalMap.containsValue(value); + } + + @Override + public void clear() { + // override as default implementation uses reverseMap + for (final Iterator it = keySet().iterator(); it.hasNext();) { + it.next(); + it.remove(); + } + } + + @Override + public SortedMap headMap(final K toKey) { + return new ViewMap(decorated(), super.headMap(toKey)); + } + + @Override + public SortedMap tailMap(final K fromKey) { + return new ViewMap(decorated(), super.tailMap(fromKey)); + } + + @Override + public SortedMap subMap(final K fromKey, final K toKey) { + return new ViewMap(decorated(), super.subMap(fromKey, toKey)); + } + + @Override + protected DualTreeBidiMap decorated() { + return (DualTreeBidiMap) super.decorated(); + } + + @Override + public K previousKey(final K key) { + return decorated().previousKey(key); + } + + @Override + public K nextKey(final K key) { + return decorated().nextKey(key); + } + } + + //----------------------------------------------------------------------- + /** + * Inner class MapIterator. + */ + protected static class BidiOrderedMapIterator implements OrderedMapIterator, ResettableIterator { + + /** The parent map */ + private final AbstractDualBidiMap parent; + + /** The iterator being decorated */ + private ListIterator> iterator; + + /** The last returned entry */ + private Map.Entry last = null; + + /** + * Constructor. + * @param parent the parent map + */ + protected BidiOrderedMapIterator(final AbstractDualBidiMap parent) { + super(); + this.parent = parent; + iterator = new ArrayList>(parent.entrySet()).listIterator(); + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public K next() { + last = iterator.next(); + return last.getKey(); + } + + @Override + public boolean hasPrevious() { + return iterator.hasPrevious(); + } + + @Override + public K previous() { + last = iterator.previous(); + return last.getKey(); + } + + @Override + public void remove() { + iterator.remove(); + parent.remove(last.getKey()); + last = null; + } + + @Override + public K getKey() { + if (last == null) { + throw new IllegalStateException( + "Iterator getKey() can only be called after next() and before remove()"); + } + return last.getKey(); + } + + @Override + public V getValue() { + if (last == null) { + throw new IllegalStateException( + "Iterator getValue() can only be called after next() and before remove()"); + } + return last.getValue(); + } + + @Override + public V setValue(final V value) { + if (last == null) { + throw new IllegalStateException( + "Iterator setValue() can only be called after next() and before remove()"); + } + if (parent.reverseMap.containsKey(value) && + parent.reverseMap.get(value) != last.getKey()) { + throw new IllegalArgumentException( + "Cannot use setValue() when the object being set is already in the map"); + } + final V oldValue = parent.put(last.getKey(), value); + // Map.Entry specifies that the behavior is undefined when the backing map + // has been modified (as we did with the put), so we also set the value + // (especially needed for IBM JDK) + last.setValue(value); + return oldValue; + } + + @Override + public void reset() { + iterator = new ArrayList>(parent.entrySet()).listIterator(); + last = null; + } + + @Override + public String toString() { + if (last != null) { + return "MapIterator[" + getKey() + "=" + getValue() + "]"; + } + return "MapIterator[]"; + } + } + + // Serialization + //----------------------------------------------------------------------- + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(normalMap); + } + + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + normalMap = new TreeMap(comparator); + reverseMap = new TreeMap(valueComparator); + @SuppressWarnings("unchecked") // will fail at runtime if the stream is incorrect + final Map map = (Map) in.readObject(); + putAll(map); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/TreeBidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/TreeBidiMap.java new file mode 100644 index 000000000..8a517e281 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/TreeBidiMap.java @@ -0,0 +1,2228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import static org.apache.commons.collections4.bidimap.TreeBidiMap.DataElement.KEY; +import static org.apache.commons.collections4.bidimap.TreeBidiMap.DataElement.VALUE; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.commons.collections4.KeyValue; +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.OrderedBidiMap; +import org.apache.commons.collections4.OrderedIterator; +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.iterators.EmptyOrderedMapIterator; +import org.apache.commons.collections4.keyvalue.UnmodifiableMapEntry; + +/** + * Red-Black tree-based implementation of BidiMap where all objects added + * implement the Comparable interface. + *

          + * This class guarantees that the map will be in both ascending key order + * and ascending value order, sorted according to the natural order for + * the key's and value's classes. + *

          + * This Map is intended for applications that need to be able to look + * up a key-value pairing by either key or value, and need to do so + * with equal efficiency. + *

          + * While that goal could be accomplished by taking a pair of TreeMaps + * and redirecting requests to the appropriate TreeMap (e.g., + * containsKey would be directed to the TreeMap that maps values to + * keys, containsValue would be directed to the TreeMap that maps keys + * to values), there are problems with that implementation. + * If the data contained in the TreeMaps is large, the cost of redundant + * storage becomes significant. The {@link DualTreeBidiMap} and + * {@link DualHashBidiMap} implementations use this approach. + *

          + * This solution keeps minimizes the data storage by holding data only once. + * The red-black algorithm is based on {@link java.util.TreeMap}, but has been modified + * to simultaneously map a tree node by key and by value. This doubles the + * cost of put operations (but so does using two TreeMaps), and nearly doubles + * the cost of remove operations (there is a savings in that the lookup of the + * node to be removed only has to be performed once). And since only one node + * contains the key and value, storage is significantly less than that + * required by two TreeMaps. + *

          + * The Map.Entry instances returned by the appropriate methods will + * not allow setValue() and will throw an + * UnsupportedOperationException on attempts to call that method. + * + * @since 3.0 (previously DoubleOrderedMap v2.0) + * @version $Id: TreeBidiMap.java 1683951 2015-06-06 20:19:03Z tn $ + */ +public class TreeBidiMap, V extends Comparable> + implements OrderedBidiMap, Serializable { + + static enum DataElement { + KEY("key"), VALUE("value"); + + private final String description; + + /** + * Create a new TreeBidiMap.DataElement. + * + * @param description the description for the element + */ + private DataElement(final String description) { + this.description = description; + } + + @Override + public String toString() { + return description; + } + } + + private static final long serialVersionUID = 721969328361807L; + + private transient Node[] rootNode; + private transient int nodeCount = 0; + private transient int modifications = 0; + private transient Set keySet; + private transient Set valuesSet; + private transient Set> entrySet; + private transient Inverse inverse = null; + + //----------------------------------------------------------------------- + /** + * Constructs a new empty TreeBidiMap. + */ + @SuppressWarnings("unchecked") + public TreeBidiMap() { + super(); + rootNode = new Node[2]; + } + + /** + * Constructs a new TreeBidiMap by copying an existing Map. + * + * @param map the map to copy + * @throws ClassCastException if the keys/values in the map are + * not Comparable or are not mutually comparable + * @throws NullPointerException if any key or value in the map is null + */ + public TreeBidiMap(final Map map) { + this(); + putAll(map); + } + + //----------------------------------------------------------------------- + /** + * Returns the number of key-value mappings in this map. + * + * @return the number of key-value mappings in this map + */ + @Override + public int size() { + return nodeCount; + } + + /** + * Checks whether the map is empty or not. + * + * @return true if the map is empty + */ + @Override + public boolean isEmpty() { + return nodeCount == 0; + } + + /** + * Checks whether this map contains the a mapping for the specified key. + *

          + * The key must implement Comparable. + * + * @param key key whose presence in this map is to be tested + * @return true if this map contains a mapping for the specified key + * @throws ClassCastException if the key is of an inappropriate type + * @throws NullPointerException if the key is null + */ + @Override + public boolean containsKey(final Object key) { + checkKey(key); + return lookupKey(key) != null; + } + + /** + * Checks whether this map contains the a mapping for the specified value. + *

          + * The value must implement Comparable. + * + * @param value value whose presence in this map is to be tested + * @return true if this map contains a mapping for the specified value + * @throws ClassCastException if the value is of an inappropriate type + * @throws NullPointerException if the value is null + */ + @Override + public boolean containsValue(final Object value) { + checkValue(value); + return lookupValue(value) != null; + } + + /** + * Gets the value to which this map maps the specified key. + * Returns null if the map contains no mapping for this key. + *

          + * The key must implement Comparable. + * + * @param key key whose associated value is to be returned + * @return the value to which this map maps the specified key, + * or null if the map contains no mapping for this key + * @throws ClassCastException if the key is of an inappropriate type + * @throws NullPointerException if the key is null + */ + @Override + public V get(final Object key) { + checkKey(key); + final Node node = lookupKey(key); + return node == null ? null : node.getValue(); + } + + /** + * Puts the key-value pair into the map, replacing any previous pair. + *

          + * When adding a key-value pair, the value may already exist in the map + * against a different key. That mapping is removed, to ensure that the + * value only occurs once in the inverse map. + *

          +     *  BidiMap map1 = new TreeBidiMap();
          +     *  map.put("A","B");  // contains A mapped to B, as per Map
          +     *  map.put("A","C");  // contains A mapped to C, as per Map
          +     *
          +     *  BidiMap map2 = new TreeBidiMap();
          +     *  map.put("A","B");  // contains A mapped to B, as per Map
          +     *  map.put("C","B");  // contains C mapped to B, key A is removed
          +     * 
          + *

          + * Both key and value must implement Comparable. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value for the key + * @throws ClassCastException if the key is of an inappropriate type + * @throws NullPointerException if the key is null + */ + @Override + public V put(final K key, final V value) { + final V result = get(key); + doPut(key, value); + return result; + } + + /** + * Puts all the mappings from the specified map into this map. + *

          + * All keys and values must implement Comparable. + * + * @param map the map to copy from + */ + @Override + public void putAll(final Map map) { + for (final Map.Entry e : map.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + /** + * Removes the mapping for this key from this map if present. + *

          + * The key must implement Comparable. + * + * @param key key whose mapping is to be removed from the map. + * @return previous value associated with specified key, + * or null if there was no mapping for key. + * @throws ClassCastException if the key is of an inappropriate type + * @throws NullPointerException if the key is null + */ + @Override + public V remove(final Object key) { + return doRemoveKey(key); + } + + /** + * Removes all mappings from this map. + */ + @Override + public void clear() { + modify(); + + nodeCount = 0; + rootNode[KEY.ordinal()] = null; + rootNode[VALUE.ordinal()] = null; + } + + //----------------------------------------------------------------------- + /** + * Returns the key to which this map maps the specified value. + * Returns null if the map contains no mapping for this value. + *

          + * The value must implement Comparable. + * + * @param value value whose associated key is to be returned. + * @return the key to which this map maps the specified value, + * or null if the map contains no mapping for this value. + * @throws ClassCastException if the value is of an inappropriate type + * @throws NullPointerException if the value is null + */ + @Override + public K getKey(final Object value) { + checkValue(value); + final Node node = lookupValue(value); + return node == null ? null : node.getKey(); + } + + /** + * Removes the mapping for this value from this map if present. + *

          + * The value must implement Comparable. + * + * @param value value whose mapping is to be removed from the map + * @return previous key associated with specified value, + * or null if there was no mapping for value. + * @throws ClassCastException if the value is of an inappropriate type + * @throws NullPointerException if the value is null + */ + @Override + public K removeValue(final Object value) { + return doRemoveValue(value); + } + + //----------------------------------------------------------------------- + /** + * Gets the first (lowest) key currently in this map. + * + * @return the first (lowest) key currently in this sorted map + * @throws NoSuchElementException if this map is empty + */ + @Override + public K firstKey() { + if (nodeCount == 0) { + throw new NoSuchElementException("Map is empty"); + } + return leastNode(rootNode[KEY.ordinal()], KEY).getKey(); + } + + /** + * Gets the last (highest) key currently in this map. + * + * @return the last (highest) key currently in this sorted map + * @throws NoSuchElementException if this map is empty + */ + @Override + public K lastKey() { + if (nodeCount == 0) { + throw new NoSuchElementException("Map is empty"); + } + return greatestNode(rootNode[KEY.ordinal()], KEY).getKey(); + } + + /** + * Gets the next key after the one specified. + *

          + * The key must implement Comparable. + * + * @param key the key to search for next from + * @return the next key, null if no match or at end + */ + @Override + public K nextKey(final K key) { + checkKey(key); + final Node node = nextGreater(lookupKey(key), KEY); + return node == null ? null : node.getKey(); + } + + /** + * Gets the previous key before the one specified. + *

          + * The key must implement Comparable. + * + * @param key the key to search for previous from + * @return the previous key, null if no match or at start + */ + @Override + public K previousKey(final K key) { + checkKey(key); + final Node node = nextSmaller(lookupKey(key), KEY); + return node == null ? null : node.getKey(); + } + + //----------------------------------------------------------------------- + /** + * Returns a set view of the keys contained in this map in key order. + *

          + * The set is backed by the map, so changes to the map are reflected in + * the set, and vice-versa. If the map is modified while an iteration over + * the set is in progress, the results of the iteration are undefined. + *

          + * The set supports element removal, which removes the corresponding mapping + * from the map. It does not support the add or addAll operations. + * + * @return a set view of the keys contained in this map. + */ + @Override + public Set keySet() { + if (keySet == null) { + keySet = new KeyView(KEY); + } + return keySet; + } + + //----------------------------------------------------------------------- + /** + * Returns a set view of the values contained in this map in key order. + * The returned object can be cast to a Set. + *

          + * The set is backed by the map, so changes to the map are reflected in + * the set, and vice-versa. If the map is modified while an iteration over + * the set is in progress, the results of the iteration are undefined. + *

          + * The set supports element removal, which removes the corresponding mapping + * from the map. It does not support the add or addAll operations. + * + * @return a set view of the values contained in this map. + */ + @Override + public Set values() { + if (valuesSet == null) { + valuesSet = new ValueView(KEY); + } + return valuesSet; + } + + //----------------------------------------------------------------------- + /** + * Returns a set view of the entries contained in this map in key order. + * For simple iteration through the map, the MapIterator is quicker. + *

          + * The set is backed by the map, so changes to the map are reflected in + * the set, and vice-versa. If the map is modified while an iteration over + * the set is in progress, the results of the iteration are undefined. + *

          + * The set supports element removal, which removes the corresponding mapping + * from the map. It does not support the add or addAll operations. + * The returned MapEntry objects do not support setValue. + * + * @return a set view of the values contained in this map. + */ + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = new EntryView(); + } + return entrySet; + } + + //----------------------------------------------------------------------- + @Override + public OrderedMapIterator mapIterator() { + if (isEmpty()) { + return EmptyOrderedMapIterator.emptyOrderedMapIterator(); + } + return new ViewMapIterator(KEY); + } + + //----------------------------------------------------------------------- + /** + * Gets the inverse map for comparison. + * + * @return the inverse map + */ + @Override + public OrderedBidiMap inverseBidiMap() { + if (inverse == null) { + inverse = new Inverse(); + } + return inverse; + } + + //----------------------------------------------------------------------- + /** + * Compares for equals as per the API. + * + * @param obj the object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object obj) { + return this.doEquals(obj, KEY); + } + + /** + * Gets the hash code value for this map as per the API. + * + * @return the hash code value for this map + */ + @Override + public int hashCode() { + return this.doHashCode(KEY); + } + + /** + * Returns a string version of this Map in standard format. + * + * @return a standard format string version of the map + */ + @Override + public String toString() { + return this.doToString(KEY); + } + + //----------------------------------------------------------------------- + /** + * Put logic. + * + * @param key the key, always the main map key + * @param value the value, always the main map value + */ + private void doPut(final K key, final V value) { + checkKeyAndValue(key, value); + + // store previous and remove previous mappings + doRemoveKey(key); + doRemoveValue(value); + + Node node = rootNode[KEY.ordinal()]; + if (node == null) { + // map is empty + final Node root = new Node(key, value); + rootNode[KEY.ordinal()] = root; + rootNode[VALUE.ordinal()] = root; + grow(); + + } else { + // add new mapping + while (true) { + final int cmp = compare(key, node.getKey()); + + if (cmp == 0) { + // shouldn't happen + throw new IllegalArgumentException("Cannot store a duplicate key (\"" + key + "\") in this Map"); + } else if (cmp < 0) { + if (node.getLeft(KEY) != null) { + node = node.getLeft(KEY); + } else { + final Node newNode = new Node(key, value); + + insertValue(newNode); + node.setLeft(newNode, KEY); + newNode.setParent(node, KEY); + doRedBlackInsert(newNode, KEY); + grow(); + + break; + } + } else { // cmp > 0 + if (node.getRight(KEY) != null) { + node = node.getRight(KEY); + } else { + final Node newNode = new Node(key, value); + + insertValue(newNode); + node.setRight(newNode, KEY); + newNode.setParent(node, KEY); + doRedBlackInsert(newNode, KEY); + grow(); + + break; + } + } + } + } + } + + private V doRemoveKey(final Object key) { + final Node node = lookupKey(key); + if (node == null) { + return null; + } + doRedBlackDelete(node); + return node.getValue(); + } + + private K doRemoveValue(final Object value) { + final Node node = lookupValue(value); + if (node == null) { + return null; + } + doRedBlackDelete(node); + return node.getKey(); + } + + /** + * do the actual lookup of a piece of data + * + * @param data the key or value to be looked up + * @param index the KEY or VALUE int + * @return the desired Node, or null if there is no mapping of the + * specified data + */ + @SuppressWarnings("unchecked") + private > Node lookup(final Object data, final DataElement dataElement) { + Node rval = null; + Node node = rootNode[dataElement.ordinal()]; + + while (node != null) { + final int cmp = compare((T) data, (T) node.getData(dataElement)); + if (cmp == 0) { + rval = node; + break; + } else { + node = cmp < 0 ? node.getLeft(dataElement) : node.getRight(dataElement); + } + } + + return rval; + } + + private Node lookupKey(final Object key) { + return this.lookup(key, KEY); + } + + private Node lookupValue(final Object value) { + return this.lookup(value, VALUE); + } + + /** + * get the next larger node from the specified node + * + * @param node the node to be searched from + * @param index the KEY or VALUE int + * @return the specified node + */ + private Node nextGreater(final Node node, final DataElement dataElement) { + Node rval; + if (node == null) { + rval = null; + } else if (node.getRight(dataElement) != null) { + // everything to the node's right is larger. The least of + // the right node's descendants is the next larger node + rval = leastNode(node.getRight(dataElement), dataElement); + } else { + // traverse up our ancestry until we find an ancestor that + // is null or one whose left child is our ancestor. If we + // find a null, then this node IS the largest node in the + // tree, and there is no greater node. Otherwise, we are + // the largest node in the subtree on that ancestor's left + // ... and that ancestor is the next greatest node + Node parent = node.getParent(dataElement); + Node child = node; + + while (parent != null && child == parent.getRight(dataElement)) { + child = parent; + parent = parent.getParent(dataElement); + } + rval = parent; + } + return rval; + } + + /** + * get the next larger node from the specified node + * + * @param node the node to be searched from + * @param index the KEY or VALUE int + * @return the specified node + */ + private Node nextSmaller(final Node node, final DataElement dataElement) { + Node rval; + if (node == null) { + rval = null; + } else if (node.getLeft(dataElement) != null) { + // everything to the node's left is smaller. The greatest of + // the left node's descendants is the next smaller node + rval = greatestNode(node.getLeft(dataElement), dataElement); + } else { + // traverse up our ancestry until we find an ancestor that + // is null or one whose right child is our ancestor. If we + // find a null, then this node IS the largest node in the + // tree, and there is no greater node. Otherwise, we are + // the largest node in the subtree on that ancestor's right + // ... and that ancestor is the next greatest node + Node parent = node.getParent(dataElement); + Node child = node; + + while (parent != null && child == parent.getLeft(dataElement)) { + child = parent; + parent = parent.getParent(dataElement); + } + rval = parent; + } + return rval; + } + + //----------------------------------------------------------------------- + + /** + * Compare two objects + * + * @param o1 the first object + * @param o2 the second object + * + * @return negative value if o1 < o2; 0 if o1 == o2; positive + * value if o1 > o2 + */ + private static > int compare(final T o1, final T o2) { + return o1.compareTo(o2); + } + + /** + * Find the least node from a given node. + * + * @param node the node from which we will start searching + * @param index the KEY or VALUE int + * @return the smallest node, from the specified node, in the + * specified mapping + */ + private Node leastNode(final Node node, final DataElement dataElement) { + Node rval = node; + if (rval != null) { + while (rval.getLeft(dataElement) != null) { + rval = rval.getLeft(dataElement); + } + } + return rval; + } + + /** + * Find the greatest node from a given node. + * + * @param node the node from which we will start searching + * @param index the KEY or VALUE int + * @return the greatest node, from the specified node + */ + private Node greatestNode(final Node node, final DataElement dataElement) { + Node rval = node; + if (rval != null) { + while (rval.getRight(dataElement) != null) { + rval = rval.getRight(dataElement); + } + } + return rval; + } + + /** + * copy the color from one node to another, dealing with the fact + * that one or both nodes may, in fact, be null + * + * @param from the node whose color we're copying; may be null + * @param to the node whose color we're changing; may be null + * @param index the KEY or VALUE int + */ + private void copyColor(final Node from, final Node to, final DataElement dataElement) { + if (to != null) { + if (from == null) { + // by default, make it black + to.setBlack(dataElement); + } else { + to.copyColor(from, dataElement); + } + } + } + + /** + * is the specified node red? if the node does not exist, no, it's + * black, thank you + * + * @param node the node (may be null) in question + * @param index the KEY or VALUE int + */ + private static boolean isRed(final Node node, final DataElement dataElement) { + return node != null && node.isRed(dataElement); + } + + /** + * is the specified black red? if the node does not exist, sure, + * it's black, thank you + * + * @param node the node (may be null) in question + * @param index the KEY or VALUE int + */ + private static boolean isBlack(final Node node, final DataElement dataElement) { + return node == null || node.isBlack(dataElement); + } + + /** + * force a node (if it exists) red + * + * @param node the node (may be null) in question + * @param index the KEY or VALUE int + */ + private static void makeRed(final Node node, final DataElement dataElement) { + if (node != null) { + node.setRed(dataElement); + } + } + + /** + * force a node (if it exists) black + * + * @param node the node (may be null) in question + * @param index the KEY or VALUE int + */ + private static void makeBlack(final Node node, final DataElement dataElement) { + if (node != null) { + node.setBlack(dataElement); + } + } + + /** + * get a node's grandparent. mind you, the node, its parent, or + * its grandparent may not exist. no problem + * + * @param node the node (may be null) in question + * @param index the KEY or VALUE int + */ + private Node getGrandParent(final Node node, final DataElement dataElement) { + return getParent(getParent(node, dataElement), dataElement); + } + + /** + * get a node's parent. mind you, the node, or its parent, may not + * exist. no problem + * + * @param node the node (may be null) in question + * @param index the KEY or VALUE int + */ + private Node getParent(final Node node, final DataElement dataElement) { + return node == null ? null : node.getParent(dataElement); + } + + /** + * get a node's right child. mind you, the node may not exist. no + * problem + * + * @param node the node (may be null) in question + * @param index the KEY or VALUE int + */ + private Node getRightChild(final Node node, final DataElement dataElement) { + return node == null ? null : node.getRight(dataElement); + } + + /** + * get a node's left child. mind you, the node may not exist. no + * problem + * + * @param node the node (may be null) in question + * @param index the KEY or VALUE int + */ + private Node getLeftChild(final Node node, final DataElement dataElement) { + return node == null ? null : node.getLeft(dataElement); + } + + /** + * do a rotate left. standard fare in the world of balanced trees + * + * @param node the node to be rotated + * @param index the KEY or VALUE int + */ + private void rotateLeft(final Node node, final DataElement dataElement) { + final Node rightChild = node.getRight(dataElement); + node.setRight(rightChild.getLeft(dataElement), dataElement); + + if (rightChild.getLeft(dataElement) != null) { + rightChild.getLeft(dataElement).setParent(node, dataElement); + } + rightChild.setParent(node.getParent(dataElement), dataElement); + + if (node.getParent(dataElement) == null) { + // node was the root ... now its right child is the root + rootNode[dataElement.ordinal()] = rightChild; + } else if (node.getParent(dataElement).getLeft(dataElement) == node) { + node.getParent(dataElement).setLeft(rightChild, dataElement); + } else { + node.getParent(dataElement).setRight(rightChild, dataElement); + } + + rightChild.setLeft(node, dataElement); + node.setParent(rightChild, dataElement); + } + + /** + * do a rotate right. standard fare in the world of balanced trees + * + * @param node the node to be rotated + * @param index the KEY or VALUE int + */ + private void rotateRight(final Node node, final DataElement dataElement) { + final Node leftChild = node.getLeft(dataElement); + node.setLeft(leftChild.getRight(dataElement), dataElement); + if (leftChild.getRight(dataElement) != null) { + leftChild.getRight(dataElement).setParent(node, dataElement); + } + leftChild.setParent(node.getParent(dataElement), dataElement); + + if (node.getParent(dataElement) == null) { + // node was the root ... now its left child is the root + rootNode[dataElement.ordinal()] = leftChild; + } else if (node.getParent(dataElement).getRight(dataElement) == node) { + node.getParent(dataElement).setRight(leftChild, dataElement); + } else { + node.getParent(dataElement).setLeft(leftChild, dataElement); + } + + leftChild.setRight(node, dataElement); + node.setParent(leftChild, dataElement); + } + + /** + * complicated red-black insert stuff. Based on Sun's TreeMap + * implementation, though it's barely recognizable any more + * + * @param insertedNode the node to be inserted + * @param dataElement the KEY or VALUE int + */ + private void doRedBlackInsert(final Node insertedNode, final DataElement dataElement) { + Node currentNode = insertedNode; + makeRed(currentNode, dataElement); + + while (currentNode != null + && currentNode != rootNode[dataElement.ordinal()] + && isRed(currentNode.getParent(dataElement), dataElement)) { + if (currentNode.isLeftChild(dataElement)) { + final Node y = getRightChild(getGrandParent(currentNode, dataElement), dataElement); + + if (isRed(y, dataElement)) { + makeBlack(getParent(currentNode, dataElement), dataElement); + makeBlack(y, dataElement); + makeRed(getGrandParent(currentNode, dataElement), dataElement); + + currentNode = getGrandParent(currentNode, dataElement); + } else { + //dead code? + if (currentNode.isRightChild(dataElement)) { + currentNode = getParent(currentNode, dataElement); + + rotateLeft(currentNode, dataElement); + } + + makeBlack(getParent(currentNode, dataElement), dataElement); + makeRed(getGrandParent(currentNode, dataElement), dataElement); + + if (getGrandParent(currentNode, dataElement) != null) { + rotateRight(getGrandParent(currentNode, dataElement), dataElement); + } + } + } else { + + // just like clause above, except swap left for right + final Node y = getLeftChild(getGrandParent(currentNode, dataElement), dataElement); + + if (isRed(y, dataElement)) { + makeBlack(getParent(currentNode, dataElement), dataElement); + makeBlack(y, dataElement); + makeRed(getGrandParent(currentNode, dataElement), dataElement); + + currentNode = getGrandParent(currentNode, dataElement); + } else { + //dead code? + if (currentNode.isLeftChild(dataElement)) { + currentNode = getParent(currentNode, dataElement); + + rotateRight(currentNode, dataElement); + } + + makeBlack(getParent(currentNode, dataElement), dataElement); + makeRed(getGrandParent(currentNode, dataElement), dataElement); + + if (getGrandParent(currentNode, dataElement) != null) { + rotateLeft(getGrandParent(currentNode, dataElement), dataElement); + } + } + } + } + + makeBlack(rootNode[dataElement.ordinal()], dataElement); + } + + /** + * complicated red-black delete stuff. Based on Sun's TreeMap + * implementation, though it's barely recognizable any more + * + * @param deletedNode the node to be deleted + */ + private void doRedBlackDelete(final Node deletedNode) { + for (final DataElement dataElement : DataElement.values()) { + // if deleted node has both left and children, swap with + // the next greater node + if (deletedNode.getLeft(dataElement) != null && deletedNode.getRight(dataElement) != null) { + swapPosition(nextGreater(deletedNode, dataElement), deletedNode, dataElement); + } + + final Node replacement = deletedNode.getLeft(dataElement) != null ? + deletedNode.getLeft(dataElement) : deletedNode.getRight(dataElement); + + if (replacement != null) { + replacement.setParent(deletedNode.getParent(dataElement), dataElement); + + if (deletedNode.getParent(dataElement) == null) { + rootNode[dataElement.ordinal()] = replacement; + } else if (deletedNode == deletedNode.getParent(dataElement).getLeft(dataElement)) { + deletedNode.getParent(dataElement).setLeft(replacement, dataElement); + } else { + deletedNode.getParent(dataElement).setRight(replacement, dataElement); + } + + deletedNode.setLeft(null, dataElement); + deletedNode.setRight(null, dataElement); + deletedNode.setParent(null, dataElement); + + if (isBlack(deletedNode, dataElement)) { + doRedBlackDeleteFixup(replacement, dataElement); + } + } else { + + // replacement is null + if (deletedNode.getParent(dataElement) == null) { + + // empty tree + rootNode[dataElement.ordinal()] = null; + } else { + + // deleted node had no children + if (isBlack(deletedNode, dataElement)) { + doRedBlackDeleteFixup(deletedNode, dataElement); + } + + if (deletedNode.getParent(dataElement) != null) { + if (deletedNode == deletedNode.getParent(dataElement).getLeft(dataElement)) { + deletedNode.getParent(dataElement).setLeft(null, dataElement); + } else { + deletedNode.getParent(dataElement).setRight(null, dataElement); + } + + deletedNode.setParent(null, dataElement); + } + } + } + } + shrink(); + } + + /** + * complicated red-black delete stuff. Based on Sun's TreeMap + * implementation, though it's barely recognizable any more. This + * rebalances the tree (somewhat, as red-black trees are not + * perfectly balanced -- perfect balancing takes longer) + * + * @param replacementNode the node being replaced + * @param dataElement the KEY or VALUE int + */ + private void doRedBlackDeleteFixup(final Node replacementNode, final DataElement dataElement) { + Node currentNode = replacementNode; + + while (currentNode != rootNode[dataElement.ordinal()] && isBlack(currentNode, dataElement)) { + if (currentNode.isLeftChild(dataElement)) { + Node siblingNode = getRightChild(getParent(currentNode, dataElement), dataElement); + + if (isRed(siblingNode, dataElement)) { + makeBlack(siblingNode, dataElement); + makeRed(getParent(currentNode, dataElement), dataElement); + rotateLeft(getParent(currentNode, dataElement), dataElement); + + siblingNode = getRightChild(getParent(currentNode, dataElement), dataElement); + } + + if (isBlack(getLeftChild(siblingNode, dataElement), dataElement) + && isBlack(getRightChild(siblingNode, dataElement), dataElement)) { + makeRed(siblingNode, dataElement); + + currentNode = getParent(currentNode, dataElement); + } else { + if (isBlack(getRightChild(siblingNode, dataElement), dataElement)) { + makeBlack(getLeftChild(siblingNode, dataElement), dataElement); + makeRed(siblingNode, dataElement); + rotateRight(siblingNode, dataElement); + + siblingNode = getRightChild(getParent(currentNode, dataElement), dataElement); + } + + copyColor(getParent(currentNode, dataElement), siblingNode, dataElement); + makeBlack(getParent(currentNode, dataElement), dataElement); + makeBlack(getRightChild(siblingNode, dataElement), dataElement); + rotateLeft(getParent(currentNode, dataElement), dataElement); + + currentNode = rootNode[dataElement.ordinal()]; + } + } else { + Node siblingNode = getLeftChild(getParent(currentNode, dataElement), dataElement); + + if (isRed(siblingNode, dataElement)) { + makeBlack(siblingNode, dataElement); + makeRed(getParent(currentNode, dataElement), dataElement); + rotateRight(getParent(currentNode, dataElement), dataElement); + + siblingNode = getLeftChild(getParent(currentNode, dataElement), dataElement); + } + + if (isBlack(getRightChild(siblingNode, dataElement), dataElement) + && isBlack(getLeftChild(siblingNode, dataElement), dataElement)) { + makeRed(siblingNode, dataElement); + + currentNode = getParent(currentNode, dataElement); + } else { + if (isBlack(getLeftChild(siblingNode, dataElement), dataElement)) { + makeBlack(getRightChild(siblingNode, dataElement), dataElement); + makeRed(siblingNode, dataElement); + rotateLeft(siblingNode, dataElement); + + siblingNode = getLeftChild(getParent(currentNode, dataElement), dataElement); + } + + copyColor(getParent(currentNode, dataElement), siblingNode, dataElement); + makeBlack(getParent(currentNode, dataElement), dataElement); + makeBlack(getLeftChild(siblingNode, dataElement), dataElement); + rotateRight(getParent(currentNode, dataElement), dataElement); + + currentNode = rootNode[dataElement.ordinal()]; + } + } + } + + makeBlack(currentNode, dataElement); + } + + /** + * swap two nodes (except for their content), taking care of + * special cases where one is the other's parent ... hey, it + * happens. + * + * @param x one node + * @param y another node + * @param dataElement the KEY or VALUE int + */ + private void swapPosition(final Node x, final Node y, final DataElement dataElement) { + // Save initial values. + final Node xFormerParent = x.getParent(dataElement); + final Node xFormerLeftChild = x.getLeft(dataElement); + final Node xFormerRightChild = x.getRight(dataElement); + final Node yFormerParent = y.getParent(dataElement); + final Node yFormerLeftChild = y.getLeft(dataElement); + final Node yFormerRightChild = y.getRight(dataElement); + final boolean xWasLeftChild = + x.getParent(dataElement) != null && x == x.getParent(dataElement).getLeft(dataElement); + final boolean yWasLeftChild = + y.getParent(dataElement) != null && y == y.getParent(dataElement).getLeft(dataElement); + + // Swap, handling special cases of one being the other's parent. + if (x == yFormerParent) { // x was y's parent + x.setParent(y, dataElement); + + if (yWasLeftChild) { + y.setLeft(x, dataElement); + y.setRight(xFormerRightChild, dataElement); + } else { + y.setRight(x, dataElement); + y.setLeft(xFormerLeftChild, dataElement); + } + } else { + x.setParent(yFormerParent, dataElement); + + if (yFormerParent != null) { + if (yWasLeftChild) { + yFormerParent.setLeft(x, dataElement); + } else { + yFormerParent.setRight(x, dataElement); + } + } + + y.setLeft(xFormerLeftChild, dataElement); + y.setRight(xFormerRightChild, dataElement); + } + + if (y == xFormerParent) { // y was x's parent + y.setParent(x, dataElement); + + if (xWasLeftChild) { + x.setLeft(y, dataElement); + x.setRight(yFormerRightChild, dataElement); + } else { + x.setRight(y, dataElement); + x.setLeft(yFormerLeftChild, dataElement); + } + } else { + y.setParent(xFormerParent, dataElement); + + if (xFormerParent != null) { + if (xWasLeftChild) { + xFormerParent.setLeft(y, dataElement); + } else { + xFormerParent.setRight(y, dataElement); + } + } + + x.setLeft(yFormerLeftChild, dataElement); + x.setRight(yFormerRightChild, dataElement); + } + + // Fix children's parent pointers + if (x.getLeft(dataElement) != null) { + x.getLeft(dataElement).setParent(x, dataElement); + } + + if (x.getRight(dataElement) != null) { + x.getRight(dataElement).setParent(x, dataElement); + } + + if (y.getLeft(dataElement) != null) { + y.getLeft(dataElement).setParent(y, dataElement); + } + + if (y.getRight(dataElement) != null) { + y.getRight(dataElement).setParent(y, dataElement); + } + + x.swapColors(y, dataElement); + + // Check if root changed + if (rootNode[dataElement.ordinal()] == x) { + rootNode[dataElement.ordinal()] = y; + } else if (rootNode[dataElement.ordinal()] == y) { + rootNode[dataElement.ordinal()] = x; + } + } + + /** + * check if an object is fit to be proper input ... has to be + * Comparable and non-null + * + * @param o the object being checked + * @param index the KEY or VALUE int (used to put the right word in the + * exception message) + * + * @throws NullPointerException if o is null + * @throws ClassCastException if o is not Comparable + */ + private static void checkNonNullComparable(final Object o, final DataElement dataElement) { + if (o == null) { + throw new NullPointerException(dataElement + " cannot be null"); + } + if (!(o instanceof Comparable)) { + throw new ClassCastException(dataElement + " must be Comparable"); + } + } + + /** + * check a key for validity (non-null and implements Comparable) + * + * @param key the key to be checked + * + * @throws NullPointerException if key is null + * @throws ClassCastException if key is not Comparable + */ + private static void checkKey(final Object key) { + checkNonNullComparable(key, KEY); + } + + /** + * check a value for validity (non-null and implements Comparable) + * + * @param value the value to be checked + * + * @throws NullPointerException if value is null + * @throws ClassCastException if value is not Comparable + */ + private static void checkValue(final Object value) { + checkNonNullComparable(value, VALUE); + } + + /** + * check a key and a value for validity (non-null and implements + * Comparable) + * + * @param key the key to be checked + * @param value the value to be checked + * + * @throws NullPointerException if key or value is null + * @throws ClassCastException if key or value is not Comparable + */ + private static void checkKeyAndValue(final Object key, final Object value) { + checkKey(key); + checkValue(value); + } + + /** + * increment the modification count -- used to check for + * concurrent modification of the map through the map and through + * an Iterator from one of its Set or Collection views + */ + private void modify() { + modifications++; + } + + /** + * bump up the size and note that the map has changed + */ + private void grow() { + modify(); + nodeCount++; + } + + /** + * decrement the size and note that the map has changed + */ + private void shrink() { + modify(); + nodeCount--; + } + + /** + * insert a node by its value + * + * @param newNode the node to be inserted + * + * @throws IllegalArgumentException if the node already exists + * in the value mapping + */ + private void insertValue(final Node newNode) throws IllegalArgumentException { + Node node = rootNode[VALUE.ordinal()]; + + while (true) { + final int cmp = compare(newNode.getValue(), node.getValue()); + + if (cmp == 0) { + throw new IllegalArgumentException( + "Cannot store a duplicate value (\"" + newNode.getData(VALUE) + "\") in this Map"); + } else if (cmp < 0) { + if (node.getLeft(VALUE) != null) { + node = node.getLeft(VALUE); + } else { + node.setLeft(newNode, VALUE); + newNode.setParent(node, VALUE); + doRedBlackInsert(newNode, VALUE); + + break; + } + } else { // cmp > 0 + if (node.getRight(VALUE) != null) { + node = node.getRight(VALUE); + } else { + node.setRight(newNode, VALUE); + newNode.setParent(node, VALUE); + doRedBlackInsert(newNode, VALUE); + + break; + } + } + } + } + + //----------------------------------------------------------------------- + /** + * Compares for equals as per the API. + * + * @param obj the object to compare to + * @param type the KEY or VALUE int + * @return true if equal + */ + private boolean doEquals(final Object obj, final DataElement dataElement) { + if (obj == this) { + return true; + } + if (obj instanceof Map == false) { + return false; + } + final Map other = (Map) obj; + if (other.size() != size()) { + return false; + } + + if (nodeCount > 0) { + try { + for (final MapIterator it = getMapIterator(dataElement); it.hasNext(); ) { + final Object key = it.next(); + final Object value = it.getValue(); + if (value.equals(other.get(key)) == false) { + return false; + } + } + } catch (final ClassCastException ex) { + return false; + } catch (final NullPointerException ex) { + return false; + } + } + return true; + } + + /** + * Gets the hash code value for this map as per the API. + * + * @param type the KEY or VALUE int + * @return the hash code value for this map + */ + private int doHashCode(final DataElement dataElement) { + int total = 0; + if (nodeCount > 0) { + for (final MapIterator it = getMapIterator(dataElement); it.hasNext(); ) { + final Object key = it.next(); + final Object value = it.getValue(); + total += key.hashCode() ^ value.hashCode(); + } + } + return total; + } + + /** + * Gets the string form of this map as per AbstractMap. + * + * @param type the KEY or VALUE int + * @return the string form of this map + */ + private String doToString(final DataElement dataElement) { + if (nodeCount == 0) { + return "{}"; + } + final StringBuilder buf = new StringBuilder(nodeCount * 32); + buf.append('{'); + final MapIterator it = getMapIterator(dataElement); + boolean hasNext = it.hasNext(); + while (hasNext) { + final Object key = it.next(); + final Object value = it.getValue(); + buf.append(key == this ? "(this Map)" : key) + .append('=') + .append(value == this ? "(this Map)" : value); + + hasNext = it.hasNext(); + if (hasNext) { + buf.append(", "); + } + } + + buf.append('}'); + return buf.toString(); + } + + private MapIterator getMapIterator(final DataElement dataElement) { + switch (dataElement) { + case KEY: + return new ViewMapIterator(KEY); + case VALUE: + return new InverseViewMapIterator(VALUE); + default: + throw new IllegalArgumentException(); + } + } + + /** + * Reads the content of the stream. + */ + @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect + private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException{ + stream.defaultReadObject(); + rootNode = new Node[2]; + int size = stream.readInt(); + for(int i = 0; i < size; i++){ + K k =(K) stream.readObject(); + V v =(V) stream.readObject(); + put(k, v); + } + } + + /** + * Writes the content to the stream for serialization. + */ + private void writeObject(final ObjectOutputStream stream) throws IOException{ + stream.defaultWriteObject(); + stream.writeInt(this.size()); + for (final Entry entry : entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + //----------------------------------------------------------------------- + /** + * A view of this map. + */ + abstract class View extends AbstractSet { + + /** Whether to return KEY or VALUE order. */ + final DataElement orderType; + + /** + * Constructor. + * @param orderType the KEY or VALUE int for the order + * @param main the main map + */ + View(final DataElement orderType) { + super(); + this.orderType = orderType; + } + + @Override + public int size() { + return TreeBidiMap.this.size(); + } + + @Override + public void clear() { + TreeBidiMap.this.clear(); + } + } + + class KeyView extends View { + + /** + * Create a new TreeBidiMap.KeyView. + */ + public KeyView(final DataElement orderType) { + super(orderType); + } + + @Override + public Iterator iterator() { + return new ViewMapIterator(orderType); + } + + @Override + public boolean contains(final Object obj) { + checkNonNullComparable(obj, KEY); + return lookupKey(obj) != null; + } + + @Override + public boolean remove(final Object o) { + return doRemoveKey(o) != null; + } + + } + + class ValueView extends View { + + /** + * Create a new TreeBidiMap.ValueView. + */ + public ValueView(final DataElement orderType) { + super(orderType); + } + + @Override + public Iterator iterator() { + return new InverseViewMapIterator(orderType); + } + + @Override + public boolean contains(final Object obj) { + checkNonNullComparable(obj, VALUE); + return lookupValue(obj) != null; + } + + @Override + public boolean remove(final Object o) { + return doRemoveValue(o) != null; + } + + } + + /** + * A view of this map. + */ + class EntryView extends View> { + + EntryView() { + super(KEY); + } + + @Override + public boolean contains(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry entry = (Map.Entry) obj; + final Object value = entry.getValue(); + final Node node = lookupKey(entry.getKey()); + return node != null && node.getValue().equals(value); + } + + @Override + public boolean remove(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry entry = (Map.Entry) obj; + final Object value = entry.getValue(); + final Node node = lookupKey(entry.getKey()); + if (node != null && node.getValue().equals(value)) { + doRedBlackDelete(node); + return true; + } + return false; + } + + @Override + public Iterator> iterator() { + return new ViewMapEntryIterator(); + } + } + + /** + * A view of this map. + */ + class InverseEntryView extends View> { + + InverseEntryView() { + super(VALUE); + } + + @Override + public boolean contains(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry entry = (Map.Entry) obj; + final Object value = entry.getValue(); + final Node node = lookupValue(entry.getKey()); + return node != null && node.getKey().equals(value); + } + + @Override + public boolean remove(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry entry = (Map.Entry) obj; + final Object value = entry.getValue(); + final Node node = lookupValue(entry.getKey()); + if (node != null && node.getKey().equals(value)) { + doRedBlackDelete(node); + return true; + } + return false; + } + + @Override + public Iterator> iterator() { + return new InverseViewMapEntryIterator(); + } + } + + //----------------------------------------------------------------------- + /** + * An iterator over the map. + */ + abstract class ViewIterator { + + /** Whether to return KEY or VALUE order. */ + private final DataElement orderType; + /** The last node returned by the iterator. */ + Node lastReturnedNode; + /** The next node to be returned by the iterator. */ + private Node nextNode; + /** The previous node in the sequence returned by the iterator. */ + private Node previousNode; + /** The modification count. */ + private int expectedModifications; + + /** + * Constructor. + * @param orderType the KEY or VALUE int for the order + * @param main the main map + */ + ViewIterator(final DataElement orderType) { + super(); + this.orderType = orderType; + expectedModifications = modifications; + nextNode = leastNode(rootNode[orderType.ordinal()], orderType); + lastReturnedNode = null; + previousNode = null; + } + + public final boolean hasNext() { + return nextNode != null; + } + + protected Node navigateNext() { + if (nextNode == null) { + throw new NoSuchElementException(); + } + if (modifications != expectedModifications) { + throw new ConcurrentModificationException(); + } + lastReturnedNode = nextNode; + previousNode = nextNode; + nextNode = nextGreater(nextNode, orderType); + return lastReturnedNode; + } + + public boolean hasPrevious() { + return previousNode != null; + } + + protected Node navigatePrevious() { + if (previousNode == null) { + throw new NoSuchElementException(); + } + if (modifications != expectedModifications) { + throw new ConcurrentModificationException(); + } + nextNode = lastReturnedNode; + if (nextNode == null) { + nextNode = nextGreater(previousNode, orderType); + } + lastReturnedNode = previousNode; + previousNode = nextSmaller(previousNode, orderType); + return lastReturnedNode; + } + + public final void remove() { + if (lastReturnedNode == null) { + throw new IllegalStateException(); + } + if (modifications != expectedModifications) { + throw new ConcurrentModificationException(); + } + doRedBlackDelete(lastReturnedNode); + expectedModifications++; + lastReturnedNode = null; + if (nextNode == null) { + previousNode = greatestNode(rootNode[orderType.ordinal()], orderType); + } else { + previousNode = nextSmaller(nextNode, orderType); + } + } + } + + //----------------------------------------------------------------------- + /** + * An iterator over the map. + */ + class ViewMapIterator extends ViewIterator implements OrderedMapIterator { + + /** + * Constructor. + */ + ViewMapIterator(final DataElement orderType) { + super(orderType); + } + + @Override + public K getKey() { + if (lastReturnedNode == null) { + throw new IllegalStateException( + "Iterator getKey() can only be called after next() and before remove()"); + } + return lastReturnedNode.getKey(); + } + + @Override + public V getValue() { + if (lastReturnedNode == null) { + throw new IllegalStateException( + "Iterator getValue() can only be called after next() and before remove()"); + } + return lastReturnedNode.getValue(); + } + + @Override + public V setValue(final V obj) { + throw new UnsupportedOperationException(); + } + + @Override + public K next() { + return navigateNext().getKey(); + } + + @Override + public K previous() { + return navigatePrevious().getKey(); + } + } + + /** + * An iterator over the map. + */ + class InverseViewMapIterator extends ViewIterator implements OrderedMapIterator { + + /** + * Create a new TreeBidiMap.InverseViewMapIterator. + */ + public InverseViewMapIterator(final DataElement orderType) { + super(orderType); + } + + @Override + public V getKey() { + if (lastReturnedNode == null) { + throw new IllegalStateException( + "Iterator getKey() can only be called after next() and before remove()"); + } + return lastReturnedNode.getValue(); + } + + @Override + public K getValue() { + if (lastReturnedNode == null) { + throw new IllegalStateException( + "Iterator getValue() can only be called after next() and before remove()"); + } + return lastReturnedNode.getKey(); + } + + @Override + public K setValue(final K obj) { + throw new UnsupportedOperationException(); + } + + @Override + public V next() { + return navigateNext().getValue(); + } + + @Override + public V previous() { + return navigatePrevious().getValue(); + } + } + + /** + * An iterator over the map entries. + */ + class ViewMapEntryIterator extends ViewIterator implements OrderedIterator> { + + /** + * Constructor. + */ + ViewMapEntryIterator() { + super(KEY); + } + + @Override + public Map.Entry next() { + return navigateNext(); + } + + @Override + public Map.Entry previous() { + return navigatePrevious(); + } + } + + /** + * An iterator over the inverse map entries. + */ + class InverseViewMapEntryIterator extends ViewIterator implements OrderedIterator> { + + /** + * Constructor. + */ + InverseViewMapEntryIterator() { + super(VALUE); + } + + @Override + public Map.Entry next() { + return createEntry(navigateNext()); + } + + @Override + public Map.Entry previous() { + return createEntry(navigatePrevious()); + } + + private Map.Entry createEntry(final Node node) { + return new UnmodifiableMapEntry(node.getValue(), node.getKey()); + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + /** + * A node used to store the data. + */ + static class Node, V extends Comparable> implements Map.Entry, KeyValue { + + private final K key; + private final V value; + private final Node[] leftNode; + private final Node[] rightNode; + private final Node[] parentNode; + private final boolean[] blackColor; + private int hashcodeValue; + private boolean calculatedHashCode; + + /** + * Make a new cell with given key and value, and with null + * links, and black (true) colors. + * + * @param key + * @param value + */ + @SuppressWarnings("unchecked") + Node(final K key, final V value) { + super(); + this.key = key; + this.value = value; + leftNode = new Node[2]; + rightNode = new Node[2]; + parentNode = new Node[2]; + blackColor = new boolean[] { true, true }; + calculatedHashCode = false; + } + + private Object getData(final DataElement dataElement) { + switch (dataElement) { + case KEY: + return getKey(); + case VALUE: + return getValue(); + default: + throw new IllegalArgumentException(); + } + } + + private void setLeft(final Node node, final DataElement dataElement) { + leftNode[dataElement.ordinal()] = node; + } + + private Node getLeft(final DataElement dataElement) { + return leftNode[dataElement.ordinal()]; + } + + private void setRight(final Node node, final DataElement dataElement) { + rightNode[dataElement.ordinal()] = node; + } + + private Node getRight(final DataElement dataElement) { + return rightNode[dataElement.ordinal()]; + } + + /** + * Set this node's parent node. + * + * @param node the new parent node + * @param index the KEY or VALUE int + */ + private void setParent(final Node node, final DataElement dataElement) { + parentNode[dataElement.ordinal()] = node; + } + + /** + * Get the parent node. + * + * @param index the KEY or VALUE int + * @return the parent node, may be null + */ + private Node getParent(final DataElement dataElement) { + return parentNode[dataElement.ordinal()]; + } + + /** + * Exchange colors with another node. + * + * @param node the node to swap with + * @param index the KEY or VALUE int + */ + private void swapColors(final Node node, final DataElement dataElement) { + // Swap colors -- old hacker's trick + blackColor[dataElement.ordinal()] ^= node.blackColor[dataElement.ordinal()]; + node.blackColor[dataElement.ordinal()] ^= blackColor[dataElement.ordinal()]; + blackColor[dataElement.ordinal()] ^= node.blackColor[dataElement.ordinal()]; + } + + /** + * Is this node black? + * + * @param index the KEY or VALUE int + * @return true if black (which is represented as a true boolean) + */ + private boolean isBlack(final DataElement dataElement) { + return blackColor[dataElement.ordinal()]; + } + + /** + * Is this node red? + * + * @param index the KEY or VALUE int + * @return true if non-black + */ + private boolean isRed(final DataElement dataElement) { + return !blackColor[dataElement.ordinal()]; + } + + /** + * Make this node black. + * + * @param index the KEY or VALUE int + */ + private void setBlack(final DataElement dataElement) { + blackColor[dataElement.ordinal()] = true; + } + + /** + * Make this node red. + * + * @param index the KEY or VALUE int + */ + private void setRed(final DataElement dataElement) { + blackColor[dataElement.ordinal()] = false; + } + + /** + * Make this node the same color as another + * + * @param node the node whose color we're adopting + * @param index the KEY or VALUE int + */ + private void copyColor(final Node node, final DataElement dataElement) { + blackColor[dataElement.ordinal()] = node.blackColor[dataElement.ordinal()]; + } + + private boolean isLeftChild(final DataElement dataElement) { + return parentNode[dataElement.ordinal()] != null + && parentNode[dataElement.ordinal()].leftNode[dataElement.ordinal()] == this; + } + + private boolean isRightChild(final DataElement dataElement) { + return parentNode[dataElement.ordinal()] != null + && parentNode[dataElement.ordinal()].rightNode[dataElement.ordinal()] == this; + } + + //------------------------------------------------------------------- + /** + * Gets the key. + * + * @return the key corresponding to this entry. + */ + @Override + public K getKey() { + return key; + } + + /** + * Gets the value. + * + * @return the value corresponding to this entry. + */ + @Override + public V getValue() { + return value; + } + + /** + * Optional operation that is not permitted in this implementation + * + * @param ignored + * @return does not return + * @throws UnsupportedOperationException always + */ + @Override + public V setValue(final V ignored) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Map.Entry.setValue is not supported"); + } + + /** + * Compares the specified object with this entry for equality. + * Returns true if the given object is also a map entry and + * the two entries represent the same mapping. + * + * @param obj the object to be compared for equality with this entry. + * @return true if the specified object is equal to this entry. + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Map.Entry)) { + return false; + } + final Map.Entry e = (Map.Entry) obj; + return getKey().equals(e.getKey()) && getValue().equals(e.getValue()); + } + + /** + * @return the hash code value for this map entry. + */ + @Override + public int hashCode() { + if (!calculatedHashCode) { + hashcodeValue = getKey().hashCode() ^ getValue().hashCode(); + calculatedHashCode = true; + } + return hashcodeValue; + } + } + + //----------------------------------------------------------------------- + /** + * The inverse map implementation. + */ + class Inverse implements OrderedBidiMap { + + /** Store the keySet once created. */ + private Set inverseKeySet; + /** Store the valuesSet once created. */ + private Set inverseValuesSet; + /** Store the entrySet once created. */ + private Set> inverseEntrySet; + + @Override + public int size() { + return TreeBidiMap.this.size(); + } + + @Override + public boolean isEmpty() { + return TreeBidiMap.this.isEmpty(); + } + + @Override + public K get(final Object key) { + return TreeBidiMap.this.getKey(key); + } + + @Override + public V getKey(final Object value) { + return TreeBidiMap.this.get(value); + } + + @Override + public boolean containsKey(final Object key) { + return TreeBidiMap.this.containsValue(key); + } + + @Override + public boolean containsValue(final Object value) { + return TreeBidiMap.this.containsKey(value); + } + + @Override + public V firstKey() { + if (TreeBidiMap.this.nodeCount == 0) { + throw new NoSuchElementException("Map is empty"); + } + return leastNode(TreeBidiMap.this.rootNode[VALUE.ordinal()], VALUE).getValue(); + } + + @Override + public V lastKey() { + if (TreeBidiMap.this.nodeCount == 0) { + throw new NoSuchElementException("Map is empty"); + } + return greatestNode(TreeBidiMap.this.rootNode[VALUE.ordinal()], VALUE).getValue(); + } + + @Override + public V nextKey(final V key) { + checkKey(key); + final Node node = nextGreater(TreeBidiMap.this.lookup(key, VALUE), VALUE); + return node == null ? null : node.getValue(); + } + + @Override + public V previousKey(final V key) { + checkKey(key); + final Node node = TreeBidiMap.this.nextSmaller(TreeBidiMap.this.lookup(key, VALUE), VALUE); + return node == null ? null : node.getValue(); + } + + @Override + public K put(final V key, final K value) { + final K result = get(key); + TreeBidiMap.this.doPut(value, key); + return result; + } + + @Override + public void putAll(final Map map) { + for (final Map.Entry e : map.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + @Override + public K remove(final Object key) { + return TreeBidiMap.this.removeValue(key); + } + + @Override + public V removeValue(final Object value) { + return TreeBidiMap.this.remove(value); + } + + @Override + public void clear() { + TreeBidiMap.this.clear(); + } + + @Override + public Set keySet() { + if (inverseKeySet == null) { + inverseKeySet = new ValueView(VALUE); + } + return inverseKeySet; + } + + @Override + public Set values() { + if (inverseValuesSet == null) { + inverseValuesSet = new KeyView(VALUE); + } + return inverseValuesSet; + } + + @Override + public Set> entrySet() { + if (inverseEntrySet == null) { + inverseEntrySet = new InverseEntryView(); + } + return inverseEntrySet; + } + + @Override + public OrderedMapIterator mapIterator() { + if (isEmpty()) { + return EmptyOrderedMapIterator.emptyOrderedMapIterator(); + } + return new InverseViewMapIterator(VALUE); + } + + @Override + public OrderedBidiMap inverseBidiMap() { + return TreeBidiMap.this; + } + + @Override + public boolean equals(final Object obj) { + return TreeBidiMap.this.doEquals(obj, DataElement.VALUE); + } + + @Override + public int hashCode() { + return TreeBidiMap.this.doHashCode(DataElement.VALUE); + } + + @Override + public String toString() { + return TreeBidiMap.this.doToString(DataElement.VALUE); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableBidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableBidiMap.java new file mode 100644 index 000000000..29911fa25 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableBidiMap.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableMapIterator; +import org.apache.commons.collections4.map.UnmodifiableEntrySet; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another {@link BidiMap} to ensure it can't be altered. + *

          + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableBidiMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableBidiMap + extends AbstractBidiMapDecorator implements Unmodifiable { + + /** The inverse unmodifiable map */ + private UnmodifiableBidiMap inverse; + + /** + * Factory method to create an unmodifiable map. + *

          + * If the map passed in is already unmodifiable, it is returned. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @return an unmodifiable BidiMap + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static BidiMap unmodifiableBidiMap(final BidiMap map) { + if (map instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final BidiMap tmpMap = (BidiMap) map; + return tmpMap; + } + return new UnmodifiableBidiMap(map); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableBidiMap(final BidiMap map) { + super((BidiMap) map); + } + + //----------------------------------------------------------------------- + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public V put(final K key, final V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(final Map mapToCopy) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + final Set> set = super.entrySet(); + return UnmodifiableEntrySet.unmodifiableEntrySet(set); + } + + @Override + public Set keySet() { + final Set set = super.keySet(); + return UnmodifiableSet.unmodifiableSet(set); + } + + @Override + public Set values() { + final Set set = super.values(); + return UnmodifiableSet.unmodifiableSet(set); + } + + //----------------------------------------------------------------------- + @Override + public K removeValue(final Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public MapIterator mapIterator() { + final MapIterator it = decorated().mapIterator(); + return UnmodifiableMapIterator.unmodifiableMapIterator(it); + } + + @Override + public synchronized BidiMap inverseBidiMap() { + if (inverse == null) { + inverse = new UnmodifiableBidiMap(decorated().inverseBidiMap()); + inverse.inverse = this; + } + return inverse; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableOrderedBidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableOrderedBidiMap.java new file mode 100644 index 000000000..2b4397fca --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableOrderedBidiMap.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.OrderedBidiMap; +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableOrderedMapIterator; +import org.apache.commons.collections4.map.UnmodifiableEntrySet; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another {@link OrderedBidiMap} to ensure it can't be altered. + *

          + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableOrderedBidiMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableOrderedBidiMap + extends AbstractOrderedBidiMapDecorator implements Unmodifiable { + + /** The inverse unmodifiable map */ + private UnmodifiableOrderedBidiMap inverse; + + /** + * Factory method to create an unmodifiable map. + *

          + * If the map passed in is already unmodifiable, it is returned. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @return an unmodifiable OrderedBidiMap + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static OrderedBidiMap unmodifiableOrderedBidiMap( + final OrderedBidiMap map) { + if (map instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final OrderedBidiMap tmpMap = (OrderedBidiMap) map; + return tmpMap; + } + return new UnmodifiableOrderedBidiMap(map); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableOrderedBidiMap(final OrderedBidiMap map) { + super((OrderedBidiMap) map); + } + + //----------------------------------------------------------------------- + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public V put(final K key, final V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(final Map mapToCopy) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + final Set> set = super.entrySet(); + return UnmodifiableEntrySet.unmodifiableEntrySet(set); + } + + @Override + public Set keySet() { + final Set set = super.keySet(); + return UnmodifiableSet.unmodifiableSet(set); + } + + @Override + public Set values() { + final Set set = super.values(); + return UnmodifiableSet.unmodifiableSet(set); + } + + //----------------------------------------------------------------------- + @Override + public K removeValue(final Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public OrderedBidiMap inverseBidiMap() { + return inverseOrderedBidiMap(); + } + + //----------------------------------------------------------------------- + @Override + public OrderedMapIterator mapIterator() { + final OrderedMapIterator it = decorated().mapIterator(); + return UnmodifiableOrderedMapIterator.unmodifiableOrderedMapIterator(it); + } + + /** + * Gets an unmodifiable view of this map where the keys and values are reversed. + * + * @return an inverted unmodifiable bidirectional map + */ + public OrderedBidiMap inverseOrderedBidiMap() { + if (inverse == null) { + inverse = new UnmodifiableOrderedBidiMap(decorated().inverseBidiMap()); + inverse.inverse = this; + } + return inverse; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableSortedBidiMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableSortedBidiMap.java new file mode 100644 index 000000000..c2f795593 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/UnmodifiableSortedBidiMap.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.bidimap; + +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; + +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.SortedBidiMap; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableOrderedMapIterator; +import org.apache.commons.collections4.map.UnmodifiableEntrySet; +import org.apache.commons.collections4.map.UnmodifiableSortedMap; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another {@link SortedBidiMap} to ensure it can't be altered. + *

          + * Attempts to modify it will result in an {@link UnsupportedOperationException}. + * + * @since 3.0 + * @version $Id: UnmodifiableSortedBidiMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableSortedBidiMap + extends AbstractSortedBidiMapDecorator implements Unmodifiable { + + /** The inverse unmodifiable map */ + private UnmodifiableSortedBidiMap inverse; + + /** + * Factory method to create an unmodifiable map. + *

          + * If the map passed in is already unmodifiable, it is returned. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @return an unmodifiable SortedBidiMap + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static SortedBidiMap unmodifiableSortedBidiMap(final SortedBidiMap map) { + if (map instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final SortedBidiMap tmpMap = (SortedBidiMap) map; + return tmpMap; + } + return new UnmodifiableSortedBidiMap(map); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableSortedBidiMap(final SortedBidiMap map) { + super((SortedBidiMap) map); + } + + //----------------------------------------------------------------------- + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public V put(final K key, final V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(final Map mapToCopy) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + final Set> set = super.entrySet(); + return UnmodifiableEntrySet.unmodifiableEntrySet(set); + } + + @Override + public Set keySet() { + final Set set = super.keySet(); + return UnmodifiableSet.unmodifiableSet(set); + } + + @Override + public Set values() { + final Set set = super.values(); + return UnmodifiableSet.unmodifiableSet(set); + } + + //----------------------------------------------------------------------- + @Override + public K removeValue(final Object value) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + @Override + public OrderedMapIterator mapIterator() { + final OrderedMapIterator it = decorated().mapIterator(); + return UnmodifiableOrderedMapIterator.unmodifiableOrderedMapIterator(it); + } + + //----------------------------------------------------------------------- + @Override + public SortedBidiMap inverseBidiMap() { + if (inverse == null) { + inverse = new UnmodifiableSortedBidiMap(decorated().inverseBidiMap()); + inverse.inverse = this; + } + return inverse; + } + + @Override + public SortedMap subMap(final K fromKey, final K toKey) { + final SortedMap sm = decorated().subMap(fromKey, toKey); + return UnmodifiableSortedMap.unmodifiableSortedMap(sm); + } + + @Override + public SortedMap headMap(final K toKey) { + final SortedMap sm = decorated().headMap(toKey); + return UnmodifiableSortedMap.unmodifiableSortedMap(sm); + } + + @Override + public SortedMap tailMap(final K fromKey) { + final SortedMap sm = decorated().tailMap(fromKey); + return UnmodifiableSortedMap.unmodifiableSortedMap(sm); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/package-info.java new file mode 100644 index 000000000..1d70f6cf9 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/bidimap/package-info.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the + * {@link org.apache.commons.collections4.BidiMap BidiMap}, + * {@link org.apache.commons.collections4.OrderedBidiMap OrderedBidiMap} and + * {@link org.apache.commons.collections4.SortedBidiMap SortedBidiMap} interfaces. + * A BidiMap is an extension to Map that allows keys and values to be looked up with equal ease. + * One example usage is a system communicating to a legacy datasource that must convert codes + * from the new format to the old format and vice versa. + *

          + * The following implementations are provided in the package: + *

            + *
          • DualHashBidiMap - uses two HashMaps to implement BidiMap + *
          • DualLinkedHashBidiMap - uses two LinkedHashMaps to implement BidiMap + *
          • DualTreeBidiMap - uses two TreeMaps to implement SortedBidiMap + *
          • TreeBidiMap - red-black tree implementation of OrderedBidiMap + *
          + *

          + * The following decorators are provided in the package: + *

            + *
          • Unmodifiable - ensures the map cannot be altered + *
          + * + * @version $Id: package-info.java 1477745 2013-04-30 18:08:32Z tn $ + */ +package org.apache.commons.collections4.bidimap; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/collection/AbstractCollectionDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/collection/AbstractCollectionDecorator.java new file mode 100644 index 000000000..e980e22ea --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/collection/AbstractCollectionDecorator.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.collection; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +/** + * Decorates another Collection to provide additional behaviour. + *

          + * Each method call made on this Collection is forwarded to the + * decorated Collection. This class is used as a framework on which + * to build to extensions such as synchronized and unmodifiable behaviour. The + * main advantage of decoration is that one decorator can wrap any implementation + * of Collection, whereas sub-classing requires a new class to be + * written for each implementation. + *

          + * This implementation does not perform any special processing with + * {@link #iterator()}. Instead it simply returns the value from the + * wrapped collection. This may be undesirable, for example if you are trying + * to write an unmodifiable implementation it might provide a loophole. + *

          + * This implementation does not forward the hashCode and equals methods through + * to the backing object, but relies on Object's implementation. This is necessary + * to preserve the symmetry of equals. Custom definitions of equality are usually + * based on an interface, such as Set or List, so that the implementation of equals + * can cast the object being tested for equality to the custom interface. + * AbstractCollectionDecorator does not implement such custom interfaces directly; + * they are implemented only in subclasses. Therefore, forwarding equals would break + * symmetry, as the forwarding object might consider itself equal to the object being + * tested, but the reverse could not be true. This behavior is consistent with the + * JDK's collection wrappers, such as {@link java.util.Collections#unmodifiableCollection(Collection)}. + * Use an interface-specific subclass of AbstractCollectionDecorator, such as + * AbstractListDecorator, to preserve equality behavior, or override equals directly. + * + * @param the type of the elements in the collection + * @since 3.0 + * @version $Id: AbstractCollectionDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractCollectionDecorator + implements Collection, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 6249888059822088500L; + + /** The collection being decorated */ + private Collection collection; + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since 3.1 + */ + protected AbstractCollectionDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param coll the collection to decorate, must not be null + * @throws NullPointerException if the collection is null + */ + protected AbstractCollectionDecorator(final Collection coll) { + if (coll == null) { + throw new NullPointerException("Collection must not be null."); + } + this.collection = coll; + } + + /** + * Gets the collection being decorated. + * All access to the decorated collection goes via this method. + * + * @return the decorated collection + */ + protected Collection decorated() { + return collection; + } + + /** + * Sets the collection being decorated. + *

          + * NOTE: this method should only be used during deserialization + * + * @param coll the decorated collection + */ + protected void setCollection(final Collection coll) { + this.collection = coll; + } + + //----------------------------------------------------------------------- + + @Override + public boolean add(final E object) { + return decorated().add(object); + } + + @Override + public boolean addAll(final Collection coll) { + return decorated().addAll(coll); + } + + @Override + public void clear() { + decorated().clear(); + } + + @Override + public boolean contains(final Object object) { + return decorated().contains(object); + } + + @Override + public boolean isEmpty() { + return decorated().isEmpty(); + } + + @Override + public Iterator iterator() { + return decorated().iterator(); + } + + @Override + public boolean remove(final Object object) { + return decorated().remove(object); + } + + @Override + public int size() { + return decorated().size(); + } + + @Override + public Object[] toArray() { + return decorated().toArray(); + } + + @Override + public T[] toArray(final T[] object) { + return decorated().toArray(object); + } + + @Override + public boolean containsAll(final Collection coll) { + return decorated().containsAll(coll); + } + + @Override + public boolean removeAll(final Collection coll) { + return decorated().removeAll(coll); + } + + @Override + public boolean retainAll(final Collection coll) { + return decorated().retainAll(coll); + } + + @Override + public String toString() { + return decorated().toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/collection/CompositeCollection.java b/fine-commons-collections4/src/org/apache/commons/collections4/collection/CompositeCollection.java new file mode 100644 index 000000000..17c41528d --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/collection/CompositeCollection.java @@ -0,0 +1,481 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.collection; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.collections4.iterators.EmptyIterator; +import org.apache.commons.collections4.iterators.IteratorChain; +import org.apache.commons.collections4.list.UnmodifiableList; + +/** + * Decorates a collection of other collections to provide a single unified view. + *

          + * Changes made to this collection will actually be made on the decorated collection. + * Add and remove operations require the use of a pluggable strategy. If no + * strategy is provided then add and remove are unsupported. + * + * @param the type of the elements in the collection + * @since 3.0 + * @version $Id: CompositeCollection.java 1683951 2015-06-06 20:19:03Z tn $ + */ +public class CompositeCollection implements Collection, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 8417515734108306801L; + + /** CollectionMutator to handle changes to the collection */ + private CollectionMutator mutator; + + /** Collections in the composite */ + private final List> all = new ArrayList>(); + + /** + * Create an empty CompositeCollection. + */ + public CompositeCollection() { + super(); + } + + /** + * Create a Composite Collection with one collection. + * + * @param compositeCollection the Collection to be appended to the composite + */ + public CompositeCollection(final Collection compositeCollection) { + super(); + addComposited(compositeCollection); + } + + /** + * Create a Composite Collection with two collections. + * + * @param compositeCollection1 the Collection to be appended to the composite + * @param compositeCollection2 the Collection to be appended to the composite + */ + public CompositeCollection(final Collection compositeCollection1, + final Collection compositeCollection2) { + super(); + addComposited(compositeCollection1, compositeCollection2); + } + + /** + * Create a Composite Collection with an array of collections. + * + * @param compositeCollections the collections to composite + */ + public CompositeCollection(final Collection... compositeCollections) { + super(); + addComposited(compositeCollections); + } + + //----------------------------------------------------------------------- + /** + * Gets the size of this composite collection. + *

          + * This implementation calls size() on each collection. + * + * @return total number of elements in all contained containers + */ + @Override + public int size() { + int size = 0; + for (final Collection item : all) { + size += item.size(); + } + return size; + } + + /** + * Checks whether this composite collection is empty. + *

          + * This implementation calls isEmpty() on each collection. + * + * @return true if all of the contained collections are empty + */ + @Override + public boolean isEmpty() { + for (final Collection item : all) { + if (item.isEmpty() == false) { + return false; + } + } + return true; + } + + /** + * Checks whether this composite collection contains the object. + *

          + * This implementation calls contains() on each collection. + * + * @param obj the object to search for + * @return true if obj is contained in any of the contained collections + */ + @Override + public boolean contains(final Object obj) { + for (final Collection item : all) { + if (item.contains(obj)) { + return true; + } + } + return false; + } + + /** + * Gets an iterator over all the collections in this composite. + *

          + * This implementation uses an IteratorChain. + * + * @return an IteratorChain instance which supports + * remove(). Iteration occurs over contained collections in + * the order they were added, but this behavior should not be relied upon. + * @see IteratorChain + */ + @Override + public Iterator iterator() { + if (all.isEmpty()) { + return EmptyIterator.emptyIterator(); + } + final IteratorChain chain = new IteratorChain(); + for (final Collection item : all) { + chain.addIterator(item.iterator()); + } + return chain; + } + + /** + * Returns an array containing all of the elements in this composite. + * + * @return an object array of all the elements in the collection + */ + @Override + public Object[] toArray() { + final Object[] result = new Object[size()]; + int i = 0; + for (final Iterator it = iterator(); it.hasNext(); i++) { + result[i] = it.next(); + } + return result; + } + + /** + * Returns an object array, populating the supplied array if possible. + * See Collection interface for full details. + * + * @param the type of the elements in the collection + * @param array the array to use, populating if possible + * @return an array of all the elements in the collection + */ + @Override + @SuppressWarnings("unchecked") + public T[] toArray(final T[] array) { + final int size = size(); + Object[] result = null; + if (array.length >= size) { + result = array; + } else { + result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size); + } + + int offset = 0; + for (final Collection item : all) { + for (final E e : item) { + result[offset++] = e; + } + } + if (result.length > size) { + result[size] = null; + } + return (T[]) result; + } + + /** + * Adds an object to the collection, throwing UnsupportedOperationException + * unless a CollectionMutator strategy is specified. + * + * @param obj the object to add + * @return {@code true} if the collection was modified + * @throws UnsupportedOperationException if CollectionMutator hasn't been set + * @throws UnsupportedOperationException if add is unsupported + * @throws ClassCastException if the object cannot be added due to its type + * @throws NullPointerException if the object cannot be added because its null + * @throws IllegalArgumentException if the object cannot be added + */ + @Override + public boolean add(final E obj) { + if (mutator == null) { + throw new UnsupportedOperationException( + "add() is not supported on CompositeCollection without a CollectionMutator strategy"); + } + return mutator.add(this, all, obj); + } + + /** + * Removes an object from the collection, throwing UnsupportedOperationException + * unless a CollectionMutator strategy is specified. + * + * @param obj the object being removed + * @return true if the collection is changed + * @throws UnsupportedOperationException if removed is unsupported + * @throws ClassCastException if the object cannot be removed due to its type + * @throws NullPointerException if the object cannot be removed because its null + * @throws IllegalArgumentException if the object cannot be removed + */ + @Override + public boolean remove(final Object obj) { + if (mutator == null) { + throw new UnsupportedOperationException( + "remove() is not supported on CompositeCollection without a CollectionMutator strategy"); + } + return mutator.remove(this, all, obj); + } + + /** + * Checks whether this composite contains all the elements in the specified collection. + *

          + * This implementation calls contains() for each element in the + * specified collection. + * + * @param coll the collection to check for + * @return true if all elements contained + */ + @Override + public boolean containsAll(final Collection coll) { + for (final Object item : coll) { + if (contains(item) == false) { + return false; + } + } + return true; + } + + /** + * Adds a collection of elements to this collection, throwing + * UnsupportedOperationException unless a CollectionMutator strategy is specified. + * + * @param coll the collection to add + * @return true if the collection was modified + * @throws UnsupportedOperationException if CollectionMutator hasn't been set + * @throws UnsupportedOperationException if add is unsupported + * @throws ClassCastException if the object cannot be added due to its type + * @throws NullPointerException if the object cannot be added because its null + * @throws IllegalArgumentException if the object cannot be added + */ + @Override + public boolean addAll(final Collection coll) { + if (mutator == null) { + throw new UnsupportedOperationException( + "addAll() is not supported on CompositeCollection without a CollectionMutator strategy"); + } + return mutator.addAll(this, all, coll); + } + + /** + * Removes the elements in the specified collection from this composite collection. + *

          + * This implementation calls removeAll on each collection. + * + * @param coll the collection to remove + * @return true if the collection was modified + * @throws UnsupportedOperationException if removeAll is unsupported + */ + @Override + public boolean removeAll(final Collection coll) { + if (coll.size() == 0) { + return false; + } + boolean changed = false; + for (final Collection item : all) { + changed |= item.removeAll(coll); + } + return changed; + } + + /** + * Retains all the elements in the specified collection in this composite collection, + * removing all others. + *

          + * This implementation calls retainAll() on each collection. + * + * @param coll the collection to remove + * @return true if the collection was modified + * @throws UnsupportedOperationException if retainAll is unsupported + */ + @Override + public boolean retainAll(final Collection coll) { + boolean changed = false; + for (final Collection item : all) { + changed |= item.retainAll(coll); + } + return changed; + } + + /** + * Removes all of the elements from this collection . + *

          + * This implementation calls clear() on each collection. + * + * @throws UnsupportedOperationException if clear is unsupported + */ + @Override + public void clear() { + for (final Collection coll : all) { + coll.clear(); + } + } + + //----------------------------------------------------------------------- + /** + * Specify a CollectionMutator strategy instance to handle changes. + * + * @param mutator the mutator to use + */ + public void setMutator(final CollectionMutator mutator) { + this.mutator = mutator; + } + + /** + * Add these Collections to the list of collections in this composite + * + * @param compositeCollection the Collection to be appended to the composite + */ + public void addComposited(final Collection compositeCollection) { + all.add(compositeCollection); + } + + /** + * Add these Collections to the list of collections in this composite + * + * @param compositeCollection1 the Collection to be appended to the composite + * @param compositeCollection2 the Collection to be appended to the composite + */ + public void addComposited(final Collection compositeCollection1, + final Collection compositeCollection2) { + all.add(compositeCollection1); + all.add(compositeCollection2); + } + + /** + * Add these Collections to the list of collections in this composite + * + * @param compositeCollections the Collections to be appended to the composite + */ + public void addComposited(final Collection... compositeCollections) { + all.addAll(Arrays.asList(compositeCollections)); + } + + /** + * Removes a collection from the those being decorated in this composite. + * + * @param coll collection to be removed + */ + public void removeComposited(final Collection coll) { + all.remove(coll); + } + + //----------------------------------------------------------------------- + /** + * Returns a new collection containing all of the elements + * + * @return A new ArrayList containing all of the elements in this composite. + * The new collection is not backed by this composite. + */ + public Collection toCollection() { + return new ArrayList(this); + } + + /** + * Gets the collections being decorated. + * + * @return Unmodifiable list of all collections in this composite. + */ + public List> getCollections() { + return UnmodifiableList.unmodifiableList(all); + } + + /** + * Get the collection mutator to be used for this CompositeCollection. + * @return CollectionMutator + */ + protected CollectionMutator getMutator() { + return mutator; + } + + //----------------------------------------------------------------------- + /** + * Pluggable strategy to handle changes to the composite. + * + * @param the element being held in the collection + */ + public interface CollectionMutator extends Serializable { + + /** + * Called when an object is to be added to the composite. + * + * @param composite the CompositeCollection being changed + * @param collections all of the Collection instances in this CompositeCollection + * @param obj the object being added + * @return true if the collection is changed + * @throws UnsupportedOperationException if add is unsupported + * @throws ClassCastException if the object cannot be added due to its type + * @throws NullPointerException if the object cannot be added because its null + * @throws IllegalArgumentException if the object cannot be added + */ + boolean add(CompositeCollection composite, List> collections, E obj); + + /** + * Called when a collection is to be added to the composite. + * + * @param composite the CompositeCollection being changed + * @param collections all of the Collection instances in this CompositeCollection + * @param coll the collection being added + * @return true if the collection is changed + * @throws UnsupportedOperationException if add is unsupported + * @throws ClassCastException if the object cannot be added due to its type + * @throws NullPointerException if the object cannot be added because its null + * @throws IllegalArgumentException if the object cannot be added + */ + boolean addAll(CompositeCollection composite, + List> collections, + Collection coll); + + /** + * Called when an object is to be removed to the composite. + * + * @param composite the CompositeCollection being changed + * @param collections all of the Collection instances in this CompositeCollection + * @param obj the object being removed + * @return true if the collection is changed + * @throws UnsupportedOperationException if removed is unsupported + * @throws ClassCastException if the object cannot be removed due to its type + * @throws NullPointerException if the object cannot be removed because its null + * @throws IllegalArgumentException if the object cannot be removed + */ + boolean remove(CompositeCollection composite, + List> collections, + Object obj); + + } + +} + diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/collection/IndexedCollection.java b/fine-commons-collections4/src/org/apache/commons/collections4/collection/IndexedCollection.java new file mode 100644 index 000000000..3a6bc592e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/collection/IndexedCollection.java @@ -0,0 +1,260 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.collection; + +import java.util.Collection; +import java.util.HashMap; + +import org.apache.commons.collections4.MultiMap; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.map.MultiValueMap; + +/** + * An IndexedCollection is a Map-like view onto a Collection. It accepts a + * keyTransformer to define how the keys are converted from the values. + *

          + * Modifications made to this decorator modify the index as well as the + * decorated {@link Collection}. However, modifications to the underlying + * {@link Collection} will not update the index and it will get out of sync. + *

          + * If modification of the decorated {@link Collection} is unavoidable, then a + * call to {@link #reindex()} will update the index to the current contents of + * the {@link Collection}. + * + * @param the type of object in the index. + * @param the type of object in the collection. + * + * @since 4.0 + * @version $Id: IndexedCollection.java 1683018 2015-06-01 22:41:31Z tn $ + */ +public class IndexedCollection extends AbstractCollectionDecorator { + + // TODO: replace with MultiValuedMap + + /** Serialization version */ + private static final long serialVersionUID = -5512610452568370038L; + + /** The {@link Transformer} for generating index keys. */ + private final Transformer keyTransformer; + + /** The map of indexes to collected objects. */ + private final MultiMap index; + + /** The uniqueness constraint for the index. */ + private final boolean uniqueIndex; + + /** + * Create an {@link IndexedCollection} for a unique index. + *

          + * If an element is added, which maps to an existing key, an {@link IllegalArgumentException} + * will be thrown. + * + * @param the index object type. + * @param the collection type. + * @param coll the decorated {@link Collection}. + * @param keyTransformer the {@link Transformer} for generating index keys. + * @return the created {@link IndexedCollection}. + */ + public static IndexedCollection uniqueIndexedCollection(final Collection coll, + final Transformer keyTransformer) { + return new IndexedCollection(coll, keyTransformer, + MultiValueMap.multiValueMap(new HashMap>()), + true); + } + + /** + * Create an {@link IndexedCollection} for a non-unique index. + * + * @param the index object type. + * @param the collection type. + * @param coll the decorated {@link Collection}. + * @param keyTransformer the {@link Transformer} for generating index keys. + * @return the created {@link IndexedCollection}. + */ + public static IndexedCollection nonUniqueIndexedCollection(final Collection coll, + final Transformer keyTransformer) { + return new IndexedCollection(coll, keyTransformer, + MultiValueMap.multiValueMap(new HashMap>()), + false); + } + + /** + * Create a {@link IndexedCollection}. + * + * @param coll decorated {@link Collection} + * @param keyTransformer {@link Transformer} for generating index keys + * @param map map to use as index + * @param uniqueIndex if the index shall enforce uniqueness of index keys + */ + public IndexedCollection(final Collection coll, final Transformer keyTransformer, + final MultiMap map, final boolean uniqueIndex) { + super(coll); + this.keyTransformer = keyTransformer; + this.index = map; + this.uniqueIndex = uniqueIndex; + reindex(); + } + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the object maps to an existing key and the index + * enforces a uniqueness constraint + */ + @Override + public boolean add(final C object) { + final boolean added = super.add(object); + if (added) { + addToIndex(object); + } + return added; + } + + @Override + public boolean addAll(final Collection coll) { + boolean changed = false; + for (final C c: coll) { + changed |= add(c); + } + return changed; + } + + @Override + public void clear() { + super.clear(); + index.clear(); + } + + /** + * {@inheritDoc} + *

          + * Note: uses the index for fast lookup + */ + @SuppressWarnings("unchecked") + @Override + public boolean contains(final Object object) { + return index.containsKey(keyTransformer.transform((C) object)); + } + + /** + * {@inheritDoc} + *

          + * Note: uses the index for fast lookup + */ + @Override + public boolean containsAll(final Collection coll) { + for (final Object o : coll) { + if (!contains(o)) { + return false; + } + } + return true; + } + + /** + * Get the element associated with the given key. + *

          + * In case of a non-unique index, this method will return the first + * value associated with the given key. To retrieve all elements associated + * with a key, use {@link #values(Object)}. + * + * @param key key to look up + * @return element found + * @see #values(Object) + */ + public C get(final K key) { + @SuppressWarnings("unchecked") // index is a MultiMap which returns a Collection + final Collection coll = (Collection) index.get(key); + return coll == null ? null : coll.iterator().next(); + } + + /** + * Get all elements associated with the given key. + * + * @param key key to look up + * @return a collection of elements found, or null if {@code contains(key) == false} + */ + @SuppressWarnings("unchecked") // index is a MultiMap which returns a Collection + public Collection values(final K key) { + return (Collection) index.get(key); + } + + /** + * Clears the index and re-indexes the entire decorated {@link Collection}. + */ + public void reindex() { + index.clear(); + for (final C c : decorated()) { + addToIndex(c); + } + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(final Object object) { + final boolean removed = super.remove(object); + if (removed) { + removeFromIndex((C) object); + } + return removed; + } + + @Override + public boolean removeAll(final Collection coll) { + boolean changed = false; + for (final Object o : coll) { + changed |= remove(o); + } + return changed; + } + + @Override + public boolean retainAll(final Collection coll) { + final boolean changed = super.retainAll(coll); + if (changed) { + reindex(); + } + return changed; + } + + //----------------------------------------------------------------------- + + /** + * Provides checking for adding the index. + * + * @param object the object to index + * @throws IllegalArgumentException if the object maps to an existing key and the index + * enforces a uniqueness constraint + */ + private void addToIndex(final C object) { + final K key = keyTransformer.transform(object); + if (uniqueIndex && index.containsKey(key)) { + throw new IllegalArgumentException("Duplicate key in uniquely indexed collection."); + } + index.put(key, object); + } + + /** + * Removes an object from the index. + * + * @param object the object to remove + */ + private void removeFromIndex(final C object) { + index.remove(keyTransformer.transform(object)); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/collection/PredicatedCollection.java b/fine-commons-collections4/src/org/apache/commons/collections4/collection/PredicatedCollection.java new file mode 100644 index 000000000..f9d757ffe --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/collection/PredicatedCollection.java @@ -0,0 +1,444 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.collection; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +import org.apache.commons.collections4.Bag; +import org.apache.commons.collections4.MultiSet; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.bag.HashBag; +import org.apache.commons.collections4.bag.PredicatedBag; +import org.apache.commons.collections4.functors.NotNullPredicate; +import org.apache.commons.collections4.list.PredicatedList; +import org.apache.commons.collections4.multiset.HashMultiSet; +import org.apache.commons.collections4.multiset.PredicatedMultiSet; +import org.apache.commons.collections4.queue.PredicatedQueue; +import org.apache.commons.collections4.set.PredicatedSet; + +/** + * Decorates another {@link Collection} to validate that additions + * match a specified predicate. + *

          + * This collection exists to provide validation for the decorated collection. + * It is normally created to decorate an empty collection. + * If an object cannot be added to the collection, an IllegalArgumentException is thrown. + *

          + * One usage would be to ensure that no null entries are added to the collection: + *

          + * Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE);
          + * 
          + *

          + * This class is Serializable from Commons Collections 3.1. + * + * @param the type of the elements in the collection + * @since 3.0 + * @version $Id: PredicatedCollection.java 1714484 2015-11-15 18:20:41Z tn $ + */ +public class PredicatedCollection extends AbstractCollectionDecorator { + + /** Serialization version */ + private static final long serialVersionUID = -5259182142076705162L; + + /** The predicate to use */ + protected final Predicate predicate; + + /** + * Returns a Builder with the given predicate. + * + * @param the element type + * @param predicate the predicate to use + * @return a new Builder for predicated collections + * @since 4.1 + */ + public static Builder builder(final Predicate predicate) { + return new Builder(predicate); + } + + /** + * Returns a Builder with a NotNullPredicate. + * + * @param the element type + * @return a new Builder for predicated collections that ignores null values. + * @since 4.1 + */ + public static Builder notNullBuilder() { + return new Builder(NotNullPredicate.notNullPredicate()); + } + + /** + * Factory method to create a predicated (validating) collection. + *

          + * If there are any elements already in the collection being decorated, they + * are validated. + * + * @param the type of the elements in the collection + * @param coll the collection to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @return a new predicated collection + * @throws NullPointerException if collection or predicate is null + * @throws IllegalArgumentException if the collection contains invalid elements + * @since 4.0 + */ + public static PredicatedCollection predicatedCollection(final Collection coll, + final Predicate predicate) { + return new PredicatedCollection(coll, predicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

          + * If there are any elements already in the collection being decorated, they + * are validated. + * + * @param coll the collection to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @throws NullPointerException if collection or predicate is null + * @throws IllegalArgumentException if the collection contains invalid elements + */ + protected PredicatedCollection(final Collection coll, final Predicate predicate) { + super(coll); + if (predicate == null) { + throw new NullPointerException("Predicate must not be null."); + } + this.predicate = predicate; + for (final E item : coll) { + validate(item); + } + } + + /** + * Validates the object being added to ensure it matches the predicate. + *

          + * The predicate itself should not throw an exception, but return false to + * indicate that the object cannot be added. + * + * @param object the object being added + * @throws IllegalArgumentException if the add is invalid + */ + protected void validate(final E object) { + if (predicate.evaluate(object) == false) { + throw new IllegalArgumentException("Cannot add Object '" + object + "' - Predicate '" + + predicate + "' rejected it"); + } + } + + //----------------------------------------------------------------------- + /** + * Override to validate the object being added to ensure it matches + * the predicate. + * + * @param object the object being added + * @return the result of adding to the underlying collection + * @throws IllegalArgumentException if the add is invalid + */ + @Override + public boolean add(final E object) { + validate(object); + return decorated().add(object); + } + + /** + * Override to validate the objects being added to ensure they match + * the predicate. If any one fails, no update is made to the underlying + * collection. + * + * @param coll the collection being added + * @return the result of adding to the underlying collection + * @throws IllegalArgumentException if the add is invalid + */ + @Override + public boolean addAll(final Collection coll) { + for (final E item : coll) { + validate(item); + } + return decorated().addAll(coll); + } + + /** + * Builder for creating predicated collections. + *

          + * Create a Builder with a predicate to validate elements against, then add any elements + * to the builder. Elements that fail the predicate will be added to a rejected list. + * Finally create or decorate a collection using the createPredicated[List,Set,Bag,Queue] methods. + *

          + * An example: + *

          +     *   Predicate<String> predicate = NotNullPredicate.notNullPredicate();
          +     *   PredicatedCollectionBuilder<String> builder = PredicatedCollection.builder(predicate);
          +     *   builder.add("item1");
          +     *   builder.add(null);
          +     *   builder.add("item2");
          +     *   List<String> predicatedList = builder.createPredicatedList();
          +     * 
          + *

          + * At the end of the code fragment above predicatedList is protected by the predicate supplied + * to the builder and it contains item1 and item2. + *

          + * More elements can be added to the builder once a predicated collection has been created, + * but these elements will not be reflected in already created collections. + * + * @param the element type + * @since 4.1 + */ + public static class Builder { + + /** The predicate to use. */ + private final Predicate predicate; + + /** The buffer containing valid elements. */ + private final List accepted = new ArrayList(); + + /** The buffer containing rejected elements. */ + private final List rejected = new ArrayList(); + + // ----------------------------------------------------------------------- + /** + * Constructs a PredicatedCollectionBuilder with the specified Predicate. + * + * @param predicate the predicate to use + * @throws NullPointerException if predicate is null + */ + public Builder(final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + this.predicate = predicate; + } + + /** + * Adds the item to the builder. + *

          + * If the predicate is true, it is added to the list of accepted elements, + * otherwise it is added to the rejected list. + * + * @param item the element to add + * @return the PredicatedCollectionBuilder. + */ + public Builder add(final E item) { + if (predicate.evaluate(item)) { + accepted.add(item); + } else { + rejected.add(item); + } + return this; + } + + /** + * Adds all elements from the given collection to the builder. + *

          + * All elements for which the predicate evaluates to true will be added to the + * list of accepted elements, otherwise they are added to the rejected list. + * + * @param items the elements to add to the builder + * @return the PredicatedCollectionBuilder. + */ + public Builder addAll(final Collection items) { + if (items != null) { + for (E item : items) { + add(item); + } + } + return this; + } + + /** + * Create a new predicated list filled with the accepted elements. + *

          + * The builder is not modified by this method, so it is possible to create more collections + * or add more elements afterwards. Further changes will not propagate to the returned list. + * + * @return a new predicated list. + */ + public List createPredicatedList() { + return createPredicatedList(new ArrayList()); + } + + /** + * Decorates the given list with validating behavior using the predicate. All accepted elements + * are appended to the list. If the list already contains elements, they are validated. + *

          + * The builder is not modified by this method, so it is possible to create more collections + * or add more elements afterwards. Further changes will not propagate to the returned list. + * + * @param list the List to decorate, must not be null + * @return the decorated list. + * @throws NullPointerException if list is null + * @throws IllegalArgumentException if list contains invalid elements + */ + public List createPredicatedList(final List list) { + if (list == null) { + throw new NullPointerException("List must not be null."); + } + final List predicatedList = PredicatedList.predicatedList(list, predicate); + predicatedList.addAll(accepted); + return predicatedList; + } + + /** + * Create a new predicated set filled with the accepted elements. + *

          + * The builder is not modified by this method, so it is possible to create more collections + * or add more elements afterwards. Further changes will not propagate to the returned set. + * + * @return a new predicated set. + */ + public Set createPredicatedSet() { + return createPredicatedSet(new HashSet()); + } + + /** + * Decorates the given list with validating behavior using the predicate. All accepted elements + * are appended to the set. If the set already contains elements, they are validated. + *

          + * The builder is not modified by this method, so it is possible to create more collections + * or add more elements afterwards. Further changes will not propagate to the returned set. + * + * @param set the set to decorate, must not be null + * @return the decorated set. + * @throws NullPointerException if set is null + * @throws IllegalArgumentException if set contains invalid elements + */ + public Set createPredicatedSet(final Set set) { + if (set == null) { + throw new NullPointerException("Set must not be null."); + } + final PredicatedSet predicatedSet = PredicatedSet.predicatedSet(set, predicate); + predicatedSet.addAll(accepted); + return predicatedSet; + } + + /** + * Create a new predicated multiset filled with the accepted elements. + *

          + * The builder is not modified by this method, so it is possible to create more collections + * or add more elements afterwards. Further changes will not propagate to the returned multiset. + * + * @return a new predicated multiset. + */ + public MultiSet createPredicatedMultiSet() { + return createPredicatedMultiSet(new HashMultiSet()); + } + + /** + * Decorates the given multiset with validating behavior using the predicate. All accepted elements + * are appended to the multiset. If the multiset already contains elements, they are validated. + *

          + * The builder is not modified by this method, so it is possible to create more collections + * or add more elements afterwards. Further changes will not propagate to the returned multiset. + * + * @param multiset the multiset to decorate, must not be null + * @return the decorated multiset. + * @throws NullPointerException if multiset is null + * @throws IllegalArgumentException if multiset contains invalid elements + */ + public MultiSet createPredicatedMultiSet(final MultiSet multiset) { + if (multiset == null) { + throw new NullPointerException("MultiSet must not be null."); + } + final PredicatedMultiSet predicatedMultiSet = + PredicatedMultiSet.predicatedMultiSet(multiset, predicate); + predicatedMultiSet.addAll(accepted); + return predicatedMultiSet; + } + + /** + * Create a new predicated bag filled with the accepted elements. + *

          + * The builder is not modified by this method, so it is possible to create more collections + * or add more elements afterwards. Further changes will not propagate to the returned bag. + * + * @return a new predicated bag. + */ + public Bag createPredicatedBag() { + return createPredicatedBag(new HashBag()); + } + + /** + * Decorates the given bag with validating behavior using the predicate. All accepted elements + * are appended to the bag. If the bag already contains elements, they are validated. + *

          + * The builder is not modified by this method, so it is possible to create more collections + * or add more elements afterwards. Further changes will not propagate to the returned bag. + * + * @param bag the bag to decorate, must not be null + * @return the decorated bag. + * @throws NullPointerException if bag is null + * @throws IllegalArgumentException if bag contains invalid elements + */ + public Bag createPredicatedBag(final Bag bag) { + if (bag == null) { + throw new NullPointerException("Bag must not be null."); + } + final PredicatedBag predicatedBag = PredicatedBag.predicatedBag(bag, predicate); + predicatedBag.addAll(accepted); + return predicatedBag; + } + + /** + * Create a new predicated queue filled with the accepted elements. + *

          + * The builder is not modified by this method, so it is possible to create more collections + * or add more elements afterwards. Further changes will not propagate to the returned queue. + * + * @return a new predicated queue. + */ + public Queue createPredicatedQueue() { + return createPredicatedQueue(new LinkedList()); + } + + /** + * Decorates the given queue with validating behavior using the predicate. All accepted elements + * are appended to the queue. If the queue already contains elements, they are validated. + *

          + * The builder is not modified by this method, so it is possible to create more collections + * or add more elements afterwards. Further changes will not propagate to the returned queue. + * + * @param queue the queue to decorate, must not be null + * @return the decorated queue. + * @throws NullPointerException if queue is null + * @throws IllegalArgumentException if queue contains invalid elements + */ + public Queue createPredicatedQueue(final Queue queue) { + if (queue == null) { + throw new NullPointerException("queue must not be null"); + } + final PredicatedQueue predicatedQueue = PredicatedQueue.predicatedQueue(queue, predicate); + predicatedQueue.addAll(accepted); + return predicatedQueue; + } + + /** + * Returns an unmodifiable collection containing all rejected elements. + * + * @return an unmodifiable collection + */ + public Collection rejectedElements() { + return Collections.unmodifiableCollection(rejected); + } + + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/collection/SynchronizedCollection.java b/fine-commons-collections4/src/org/apache/commons/collections4/collection/SynchronizedCollection.java new file mode 100644 index 000000000..08cf956c7 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/collection/SynchronizedCollection.java @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.collection; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +/** + * Decorates another {@link Collection} to synchronize its behaviour + * for a multi-threaded environment. + *

          + * Iterators must be manually synchronized: + *

          + * synchronized (coll) {
          + *   Iterator it = coll.iterator();
          + *   // do stuff with iterator
          + * }
          + * 
          + *

          + * This class is Serializable from Commons Collections 3.1. + * + * @param the type of the elements in the collection + * @since 3.0 + * @version $Id: SynchronizedCollection.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class SynchronizedCollection implements Collection, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 2412805092710877986L; + + /** The collection to decorate */ + private final Collection collection; + /** The object to lock on, needed for List/SortedSet views */ + protected final Object lock; + + /** + * Factory method to create a synchronized collection. + * + * @param the type of the elements in the collection + * @param coll the collection to decorate, must not be null + * @return a new synchronized collection + * @throws NullPointerException if collection is null + * @since 4.0 + */ + public static SynchronizedCollection synchronizedCollection(final Collection coll) { + return new SynchronizedCollection(coll); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param collection the collection to decorate, must not be null + * @throws NullPointerException if the collection is null + */ + protected SynchronizedCollection(final Collection collection) { + if (collection == null) { + throw new NullPointerException("Collection must not be null."); + } + this.collection = collection; + this.lock = this; + } + + /** + * Constructor that wraps (not copies). + * + * @param collection the collection to decorate, must not be null + * @param lock the lock object to use, must not be null + * @throws NullPointerException if the collection or lock is null + */ + protected SynchronizedCollection(final Collection collection, final Object lock) { + if (collection == null) { + throw new NullPointerException("Collection must not be null."); + } + if (lock == null) { + throw new NullPointerException("Lock must not be null."); + } + this.collection = collection; + this.lock = lock; + } + + /** + * Gets the collection being decorated. + * + * @return the decorated collection + */ + protected Collection decorated() { + return collection; + } + + //----------------------------------------------------------------------- + + @Override + public boolean add(final E object) { + synchronized (lock) { + return decorated().add(object); + } + } + + @Override + public boolean addAll(final Collection coll) { + synchronized (lock) { + return decorated().addAll(coll); + } + } + + @Override + public void clear() { + synchronized (lock) { + decorated().clear(); + } + } + + @Override + public boolean contains(final Object object) { + synchronized (lock) { + return decorated().contains(object); + } + } + + @Override + public boolean containsAll(final Collection coll) { + synchronized (lock) { + return decorated().containsAll(coll); + } + } + + @Override + public boolean isEmpty() { + synchronized (lock) { + return decorated().isEmpty(); + } + } + + /** + * Iterators must be manually synchronized. + *

          +     * synchronized (coll) {
          +     *   Iterator it = coll.iterator();
          +     *   // do stuff with iterator
          +     * }
          +     * 
          + * + * @return an iterator that must be manually synchronized on the collection + */ + @Override + public Iterator iterator() { + return decorated().iterator(); + } + + @Override + public Object[] toArray() { + synchronized (lock) { + return decorated().toArray(); + } + } + + @Override + public T[] toArray(final T[] object) { + synchronized (lock) { + return decorated().toArray(object); + } + } + + @Override + public boolean remove(final Object object) { + synchronized (lock) { + return decorated().remove(object); + } + } + + @Override + public boolean removeAll(final Collection coll) { + synchronized (lock) { + return decorated().removeAll(coll); + } + } + + @Override + public boolean retainAll(final Collection coll) { + synchronized (lock) { + return decorated().retainAll(coll); + } + } + + @Override + public int size() { + synchronized (lock) { + return decorated().size(); + } + } + + @Override + public boolean equals(final Object object) { + synchronized (lock) { + if (object == this) { + return true; + } + return object == this || decorated().equals(object); + } + } + + @Override + public int hashCode() { + synchronized (lock) { + return decorated().hashCode(); + } + } + + @Override + public String toString() { + synchronized (lock) { + return decorated().toString(); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/collection/TransformedCollection.java b/fine-commons-collections4/src/org/apache/commons/collections4/collection/TransformedCollection.java new file mode 100644 index 000000000..9c43eca5c --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/collection/TransformedCollection.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.collection; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.collections4.Transformer; + +/** + * Decorates another {@link Collection} to transform objects that are added. + *

          + * The add methods are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must + * use the Integer form to remove objects. + *

          + * This class is Serializable from Commons Collections 3.1. + * + * @param the type of the elements in the collection + * @since 3.0 + * @version $Id: TransformedCollection.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TransformedCollection extends AbstractCollectionDecorator { + + /** Serialization version */ + private static final long serialVersionUID = 8692300188161871514L; + + /** The transformer to use */ + protected final Transformer transformer; + + /** + * Factory method to create a transforming collection. + *

          + * If there are any elements already in the collection being decorated, they + * are NOT transformed. + * Contrast this with {@link #transformedCollection(Collection, Transformer)}. + * + * @param the type of the elements in the collection + * @param coll the collection to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed collection + * @throws NullPointerException if collection or transformer is null + * @since 4.0 + */ + public static TransformedCollection transformingCollection(final Collection coll, + final Transformer transformer) { + return new TransformedCollection(coll, transformer); + } + + /** + * Factory method to create a transforming collection that will transform + * existing contents of the specified collection. + *

          + * If there are any elements already in the collection being decorated, they + * will be transformed by this method. + * Contrast this with {@link #transformingCollection(Collection, Transformer)}. + * + * @param the type of the elements in the collection + * @param collection the collection to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed Collection + * @throws NullPointerException if collection or transformer is null + * @since 4.0 + */ + public static TransformedCollection transformedCollection(final Collection collection, + final Transformer transformer) { + + final TransformedCollection decorated = new TransformedCollection(collection, transformer); + // null collection & transformer are disallowed by the constructor call above + if (collection.size() > 0) { + @SuppressWarnings("unchecked") // collection is of type E + final E[] values = (E[]) collection.toArray(); // NOPMD - false positive for generics + collection.clear(); + for (final E value : values) { + decorated.decorated().add(transformer.transform(value)); + } + } + return decorated; + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

          + * If there are any elements already in the collection being decorated, they + * are NOT transformed. + * + * @param coll the collection to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @throws NullPointerException if collection or transformer is null + */ + protected TransformedCollection(final Collection coll, final Transformer transformer) { + super(coll); + if (transformer == null) { + throw new NullPointerException("Transformer must not be null"); + } + this.transformer = transformer; + } + + /** + * Transforms an object. + *

          + * The transformer itself may throw an exception if necessary. + * + * @param object the object to transform + * @return a transformed object + */ + protected E transform(final E object) { + return transformer.transform(object); + } + + /** + * Transforms a collection. + *

          + * The transformer itself may throw an exception if necessary. + * + * @param coll the collection to transform + * @return a transformed object + */ + protected Collection transform(final Collection coll) { + final List list = new ArrayList(coll.size()); + for (final E item : coll) { + list.add(transform(item)); + } + return list; + } + + //----------------------------------------------------------------------- + @Override + public boolean add(final E object) { + return decorated().add(transform(object)); + } + + @Override + public boolean addAll(final Collection coll) { + return decorated().addAll(transform(coll)); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/collection/UnmodifiableBoundedCollection.java b/fine-commons-collections4/src/org/apache/commons/collections4/collection/UnmodifiableBoundedCollection.java new file mode 100644 index 000000000..ccbc3e3ab --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/collection/UnmodifiableBoundedCollection.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.collection; + +import java.util.Collection; +import java.util.Iterator; + +import org.apache.commons.collections4.BoundedCollection; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; + +/** + * {@link UnmodifiableBoundedCollection} decorates another + * {@link BoundedCollection} to ensure it can't be altered. + *

          + * If a BoundedCollection is first wrapped in some other collection decorator, + * such as synchronized or predicated, the BoundedCollection methods are no + * longer accessible. + * The factory on this class will attempt to retrieve the bounded nature by + * examining the package scope variables. + *

          + * This class is Serializable from Commons Collections 3.1. + *

          + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableBoundedCollection.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableBoundedCollection extends AbstractCollectionDecorator + implements BoundedCollection, Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = -7112672385450340330L; + + /** + * Factory method to create an unmodifiable bounded collection. + * + * @param the type of the elements in the collection + * @param coll the BoundedCollection to decorate, must not be null + * @return a new unmodifiable bounded collection + * @throws NullPointerException if {@code coll} is {@code null} + * @since 4.0 + */ + public static BoundedCollection unmodifiableBoundedCollection(final BoundedCollection coll) { + if (coll instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final BoundedCollection tmpColl = (BoundedCollection) coll; + return tmpColl; + } + return new UnmodifiableBoundedCollection(coll); + } + + /** + * Factory method to create an unmodifiable bounded collection. + *

          + * This method is capable of drilling down through up to 1000 other decorators + * to find a suitable BoundedCollection. + * + * @param the type of the elements in the collection + * @param coll the BoundedCollection to decorate, must not be null + * @return a new unmodifiable bounded collection + * @throws NullPointerException if coll is null + * @throws IllegalArgumentException if coll is not a {@code BoundedCollection} + * @since 4.0 + */ + @SuppressWarnings("unchecked") + public static BoundedCollection unmodifiableBoundedCollection(Collection coll) { + if (coll == null) { + throw new NullPointerException("Collection must not be null."); + } + + // handle decorators + for (int i = 0; i < 1000; i++) { // counter to prevent infinite looping + if (coll instanceof BoundedCollection) { + break; // normal loop exit + } + if (coll instanceof AbstractCollectionDecorator) { + coll = ((AbstractCollectionDecorator) coll).decorated(); + } else if (coll instanceof SynchronizedCollection) { + coll = ((SynchronizedCollection) coll).decorated(); + } + } + + if (coll instanceof BoundedCollection == false) { + throw new IllegalArgumentException("Collection is not a bounded collection."); + } + return new UnmodifiableBoundedCollection((BoundedCollection) coll); + } + + /** + * Constructor that wraps (not copies). + * + * @param coll the collection to decorate, must not be null + * @throws NullPointerException if coll is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableBoundedCollection(final BoundedCollection coll) { + super((BoundedCollection) coll); + } + + //----------------------------------------------------------------------- + @Override + public Iterator iterator() { + return UnmodifiableIterator.unmodifiableIterator(decorated().iterator()); + } + + @Override + public boolean add(final E object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + @Override + public boolean isFull() { + return decorated().isFull(); + } + + @Override + public int maxSize() { + return decorated().maxSize(); + } + + @Override + protected BoundedCollection decorated() { + return (BoundedCollection) super.decorated(); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/collection/UnmodifiableCollection.java b/fine-commons-collections4/src/org/apache/commons/collections4/collection/UnmodifiableCollection.java new file mode 100644 index 000000000..05e65f6d3 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/collection/UnmodifiableCollection.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.collection; + +import java.util.Collection; +import java.util.Iterator; + +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; + +/** + * Decorates another {@link Collection} to ensure it can't be altered. + *

          + * This class is Serializable from Commons Collections 3.1. + *

          + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @param the type of the elements in the collection + * @since 3.0 + * @version $Id: UnmodifiableCollection.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableCollection + extends AbstractCollectionDecorator + implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = -239892006883819945L; + + /** + * Factory method to create an unmodifiable collection. + *

          + * If the collection passed in is already unmodifiable, it is returned. + * + * @param the type of the elements in the collection + * @param coll the collection to decorate, must not be null + * @return an unmodifiable collection + * @throws NullPointerException if collection is null + * @since 4.0 + */ + public static Collection unmodifiableCollection(final Collection coll) { + if (coll instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final Collection tmpColl = (Collection) coll; + return tmpColl; + } + return new UnmodifiableCollection(coll); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param coll the collection to decorate, must not be null + * @throws NullPointerException if collection is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableCollection(final Collection coll) { + super((Collection) coll); + } + + //----------------------------------------------------------------------- + @Override + public Iterator iterator() { + return UnmodifiableIterator.unmodifiableIterator(decorated().iterator()); + } + + @Override + public boolean add(final E object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/collection/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/collection/package-info.java new file mode 100644 index 000000000..e9c5bbff1 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/collection/package-info.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the + * {@link java.util.Collection Collection} interface. + *

          + * The following implementations are provided in the package: + *

            + *
          • CompositeCollection - a collection that combines multiple collections into one + *
          + * The following decorators are provided in the package: + *
            + *
          • Synchronized - synchronizes method access for multi-threaded environments + *
          • Unmodifiable - ensures the collection cannot be altered + *
          • Predicated - ensures that only elements that are valid according to a predicate can be added + *
          • Transformed - transforms elements as they are added + *
          • Indexed - provides a map-like view onto another collection + *
          + * + * @version $Id: package-info.java 1477746 2013-04-30 18:11:20Z tn $ + */ +package org.apache.commons.collections4.collection; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/comparators/BooleanComparator.java b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/BooleanComparator.java new file mode 100644 index 000000000..fb4fa764b --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/BooleanComparator.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.comparators; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * A {@link Comparator} for {@link Boolean} objects that can sort either + * true or false first. + *

          + * @see #getTrueFirstComparator() + * @see #getFalseFirstComparator() + * @see #booleanComparator(boolean) + * + * @since 3.0 + * @version $Id: BooleanComparator.java 1683951 2015-06-06 20:19:03Z tn $ + */ +public final class BooleanComparator implements Comparator, Serializable { + + /** Serialization version. */ + private static final long serialVersionUID = 1830042991606340609L; + + /** Constant "true first" reference. */ + private static final BooleanComparator TRUE_FIRST = new BooleanComparator(true); + + /** Constant "false first" reference. */ + private static final BooleanComparator FALSE_FIRST = new BooleanComparator(false); + + /** true iff true values sort before false values. */ + private boolean trueFirst = false; + + //----------------------------------------------------------------------- + /** + * Returns a BooleanComparator instance that sorts + * true values before false values. + *

          + * Clients are encouraged to use the value returned from + * this method instead of constructing a new instance + * to reduce allocation and garbage collection overhead when + * multiple BooleanComparators may be used in the same + * virtual machine. + * + * @return the true first singleton BooleanComparator + */ + public static BooleanComparator getTrueFirstComparator() { + return TRUE_FIRST; + } + + /** + * Returns a BooleanComparator instance that sorts + * false values before true values. + *

          + * Clients are encouraged to use the value returned from + * this method instead of constructing a new instance + * to reduce allocation and garbage collection overhead when + * multiple BooleanComparators may be used in the same + * virtual machine. + * + * @return the false first singleton BooleanComparator + */ + public static BooleanComparator getFalseFirstComparator() { + return FALSE_FIRST; + } + + /** + * Returns a BooleanComparator instance that sorts + * trueFirst values before + * !trueFirst values. + *

          + * Clients are encouraged to use the value returned from + * this method instead of constructing a new instance + * to reduce allocation and garbage collection overhead when + * multiple BooleanComparators may be used in the same + * virtual machine. + * + * @param trueFirst when true, sort + * true Booleans before false + * @return a singleton BooleanComparator instance + * @since 4.0 + */ + public static BooleanComparator booleanComparator(final boolean trueFirst) { + return trueFirst ? TRUE_FIRST : FALSE_FIRST; + } + + //----------------------------------------------------------------------- + /** + * Creates a BooleanComparator that sorts + * false values before true values. + *

          + * Equivalent to {@link #BooleanComparator(boolean) BooleanComparator(false)}. + *

          + * Please use the static factory instead whenever possible. + */ + public BooleanComparator() { + this(false); + } + + /** + * Creates a BooleanComparator that sorts + * trueFirst values before + * !trueFirst values. + *

          + * Please use the static factories instead whenever possible. + * + * @param trueFirst when true, sort + * true boolean values before false + */ + public BooleanComparator(final boolean trueFirst) { + this.trueFirst = trueFirst; + } + + //----------------------------------------------------------------------- + /** + * Compares two non-null Boolean objects + * according to the value of {@link #sortsTrueFirst()}. + * + * @param b1 the first boolean to compare + * @param b2 the second boolean to compare + * @return negative if obj1 is less, positive if greater, zero if equal + * @throws NullPointerException when either argument null + */ + @Override + public int compare(final Boolean b1, final Boolean b2) { + final boolean v1 = b1.booleanValue(); + final boolean v2 = b2.booleanValue(); + + return (v1 ^ v2) ? ( (v1 ^ trueFirst) ? 1 : -1 ) : 0; + } + + //----------------------------------------------------------------------- + /** + * Implement a hash code for this comparator that is consistent with + * {@link #equals(Object) equals}. + * + * @return a hash code for this comparator. + */ + @Override + public int hashCode() { + final int hash = "BooleanComparator".hashCode(); + return trueFirst ? -1 * hash : hash; + } + + /** + * Returns true iff that Object is + * is a {@link Comparator} whose ordering is known to be + * equivalent to mine. + *

          + * This implementation returns true + * iff that is a {@link BooleanComparator} + * whose value of {@link #sortsTrueFirst()} is equal to mine. + * + * @param object the object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object object) { + return (this == object) || + ((object instanceof BooleanComparator) && + (this.trueFirst == ((BooleanComparator)object).trueFirst)); + } + + //----------------------------------------------------------------------- + /** + * Returns true iff + * I sort true values before + * false values. In other words, + * returns true iff + * {@link #compare(Boolean,Boolean) compare(Boolean.FALSE,Boolean.TRUE)} + * returns a positive value. + * + * @return the trueFirst flag + */ + public boolean sortsTrueFirst() { + return trueFirst; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/comparators/ComparableComparator.java b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/ComparableComparator.java new file mode 100644 index 000000000..23bc9648a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/ComparableComparator.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.comparators; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * A {@link Comparator Comparator} that compares {@link Comparable Comparable} + * objects. + *

          + * This Comparator is useful, for example, for enforcing the natural order in + * custom implementations of {@link java.util.SortedSet SortedSet} and + * {@link java.util.SortedMap SortedMap}. + *

          + * Note: In the 2.0 and 2.1 releases of Commons Collections, this class would + * throw a {@link ClassCastException} if either of the arguments to + * {@link #compare(Object, Object) compare} were null, not + * {@link Comparable Comparable}, or for which + * {@link Comparable#compareTo(Object) compareTo} gave inconsistent results. + * This is no longer the case. See {@link #compare(Object, Object) compare} for + * details. + * + * @since 2.0 + * @version $Id: ComparableComparator.java 1683951 2015-06-06 20:19:03Z tn $ + * + * @see java.util.Collections#reverseOrder() + */ +public class ComparableComparator> implements Comparator, Serializable { + + /** Serialization version. */ + private static final long serialVersionUID=-291439688585137865L; + + /** The singleton instance. */ + @SuppressWarnings("rawtypes") + public static final ComparableComparator INSTANCE = new ComparableComparator(); + + //----------------------------------------------------------------------- + /** + * Gets the singleton instance of a ComparableComparator. + *

          + * Developers are encouraged to use the comparator returned from this method + * instead of constructing a new instance to reduce allocation and GC overhead + * when multiple comparable comparators may be used in the same VM. + * + * @param the element type + * @return the singleton ComparableComparator + * @since 4.0 + */ + @SuppressWarnings("unchecked") + public static > ComparableComparator comparableComparator() { + return INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Constructor whose use should be avoided. + *

          + * Please use the {@link #comparableComparator()} method whenever possible. + */ + public ComparableComparator() { + super(); + } + + //----------------------------------------------------------------------- + /** + * Compare the two {@link Comparable Comparable} arguments. + * This method is equivalent to: + *

          ((Comparable)obj1).compareTo(obj2)
          + * + * @param obj1 the first object to compare + * @param obj2 the second object to compare + * @return negative if obj1 is less, positive if greater, zero if equal + * @throws NullPointerException if obj1 is null, + * or when ((Comparable)obj1).compareTo(obj2) does + * @throws ClassCastException if obj1 is not a Comparable, + * or when ((Comparable)obj1).compareTo(obj2) does + */ + @Override + public int compare(final E obj1, final E obj2) { + return obj1.compareTo(obj2); + } + + //----------------------------------------------------------------------- + /** + * Implement a hash code for this comparator that is consistent with + * {@link #equals(Object) equals}. + * + * @return a hash code for this comparator. + * @since 3.0 + */ + @Override + public int hashCode() { + return "ComparableComparator".hashCode(); + } + + /** + * Returns {@code true} iff that Object is is a {@link Comparator Comparator} + * whose ordering is known to be equivalent to mine. + *

          + * This implementation returns {@code true} iff + * object.{@link Object#getClass() getClass()} equals + * this.getClass(). Subclasses may want to override this behavior to remain + * consistent with the {@link Comparator#equals(Object)} contract. + * + * @param object the object to compare with + * @return {@code true} if equal + * @since 3.0 + */ + @Override + public boolean equals(final Object object) { + return this == object || + null != object && object.getClass().equals(this.getClass()); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/comparators/ComparatorChain.java b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/ComparatorChain.java new file mode 100644 index 000000000..651527a61 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/ComparatorChain.java @@ -0,0 +1,349 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.comparators; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + * A ComparatorChain is a Comparator that wraps one or more Comparators in + * sequence. The ComparatorChain calls each Comparator in sequence until either + * 1) any single Comparator returns a non-zero result (and that result is then + * returned), or 2) the ComparatorChain is exhausted (and zero is returned). + * This type of sorting is very similar to multi-column sorting in SQL, and this + * class allows Java classes to emulate that kind of behaviour when sorting a + * List. + *

          + * To further facilitate SQL-like sorting, the order of any single Comparator in + * the list can be reversed. + *

          + * Calling a method that adds new Comparators or changes the ascend/descend sort + * after compare(Object, Object) has been called will result in an + * UnsupportedOperationException. However, take care to not alter the + * underlying List of Comparators or the BitSet that defines the sort order. + *

          + * Instances of ComparatorChain are not synchronized. The class is not + * thread-safe at construction time, but it is thread-safe to perform + * multiple comparisons after all the setup operations are complete. + * + * @since 2.0 + * @version $Id: ComparatorChain.java 1683951 2015-06-06 20:19:03Z tn $ + */ +public class ComparatorChain implements Comparator, Serializable { + + /** Serialization version from Collections 2.0. */ + private static final long serialVersionUID = -721644942746081630L; + + /** The list of comparators in the chain. */ + private final List> comparatorChain; + /** Order - false (clear) = ascend; true (set) = descend. */ + private BitSet orderingBits = null; + /** Whether the chain has been "locked". */ + private boolean isLocked = false; + + //----------------------------------------------------------------------- + /** + * Construct a ComparatorChain with no Comparators. + * You must add at least one Comparator before calling + * the compare(Object,Object) method, or an + * UnsupportedOperationException is thrown + */ + public ComparatorChain() { + this(new ArrayList>(), new BitSet()); + } + + /** + * Construct a ComparatorChain with a single Comparator, + * sorting in the forward order + * + * @param comparator First comparator in the Comparator chain + */ + public ComparatorChain(final Comparator comparator) { + this(comparator, false); + } + + /** + * Construct a Comparator chain with a single Comparator, + * sorting in the given order + * + * @param comparator First Comparator in the ComparatorChain + * @param reverse false = forward sort; true = reverse sort + */ + public ComparatorChain(final Comparator comparator, final boolean reverse) { + comparatorChain = new ArrayList>(1); + comparatorChain.add(comparator); + orderingBits = new BitSet(1); + if (reverse == true) { + orderingBits.set(0); + } + } + + /** + * Construct a ComparatorChain from the Comparators in the + * List. All Comparators will default to the forward + * sort order. + * + * @param list List of Comparators + * @see #ComparatorChain(List,BitSet) + */ + public ComparatorChain(final List> list) { + this(list, new BitSet(list.size())); + } + + /** + * Construct a ComparatorChain from the Comparators in the + * given List. The sort order of each column will be + * drawn from the given BitSet. When determining the sort + * order for Comparator at index i in the List, + * the ComparatorChain will call BitSet.get(i). + * If that method returns false, the forward + * sort order is used; a return value of true + * indicates reverse sort order. + * + * @param list List of Comparators. NOTE: This constructor does not perform a + * defensive copy of the list + * @param bits Sort order for each Comparator. Extra bits are ignored, + * unless extra Comparators are added by another method. + */ + public ComparatorChain(final List> list, final BitSet bits) { + comparatorChain = list; + orderingBits = bits; + } + + //----------------------------------------------------------------------- + /** + * Add a Comparator to the end of the chain using the + * forward sort order + * + * @param comparator Comparator with the forward sort order + */ + public void addComparator(final Comparator comparator) { + addComparator(comparator, false); + } + + /** + * Add a Comparator to the end of the chain using the + * given sort order + * + * @param comparator Comparator to add to the end of the chain + * @param reverse false = forward sort order; true = reverse sort order + */ + public void addComparator(final Comparator comparator, final boolean reverse) { + checkLocked(); + + comparatorChain.add(comparator); + if (reverse == true) { + orderingBits.set(comparatorChain.size() - 1); + } + } + + /** + * Replace the Comparator at the given index, maintaining + * the existing sort order. + * + * @param index index of the Comparator to replace + * @param comparator Comparator to place at the given index + * @exception IndexOutOfBoundsException + * if index < 0 or index >= size() + */ + public void setComparator(final int index, final Comparator comparator) throws IndexOutOfBoundsException { + setComparator(index, comparator, false); + } + + /** + * Replace the Comparator at the given index in the + * ComparatorChain, using the given sort order + * + * @param index index of the Comparator to replace + * @param comparator Comparator to set + * @param reverse false = forward sort order; true = reverse sort order + */ + public void setComparator(final int index, final Comparator comparator, final boolean reverse) { + checkLocked(); + + comparatorChain.set(index,comparator); + if (reverse == true) { + orderingBits.set(index); + } else { + orderingBits.clear(index); + } + } + + /** + * Change the sort order at the given index in the + * ComparatorChain to a forward sort. + * + * @param index Index of the ComparatorChain + */ + public void setForwardSort(final int index) { + checkLocked(); + orderingBits.clear(index); + } + + /** + * Change the sort order at the given index in the + * ComparatorChain to a reverse sort. + * + * @param index Index of the ComparatorChain + */ + public void setReverseSort(final int index) { + checkLocked(); + orderingBits.set(index); + } + + /** + * Number of Comparators in the current ComparatorChain. + * + * @return Comparator count + */ + public int size() { + return comparatorChain.size(); + } + + /** + * Determine if modifications can still be made to the + * ComparatorChain. ComparatorChains cannot be modified + * once they have performed a comparison. + * + * @return true = ComparatorChain cannot be modified; false = + * ComparatorChain can still be modified. + */ + public boolean isLocked() { + return isLocked; + } + + /** + * Throws an exception if the {@link ComparatorChain} is locked. + * + * @throws UnsupportedOperationException if the {@link ComparatorChain} is locked + */ + private void checkLocked() { + if (isLocked == true) { + throw new UnsupportedOperationException( + "Comparator ordering cannot be changed after the first comparison is performed"); + } + } + + /** + * Throws an exception if the {@link ComparatorChain} is empty. + * + * @throws UnsupportedOperationException if the {@link ComparatorChain} is empty + */ + private void checkChainIntegrity() { + if (comparatorChain.size() == 0) { + throw new UnsupportedOperationException("ComparatorChains must contain at least one Comparator"); + } + } + + //----------------------------------------------------------------------- + /** + * Perform comparisons on the Objects as per + * Comparator.compare(o1,o2). + * + * @param o1 the first object to compare + * @param o2 the second object to compare + * @return -1, 0, or 1 + * @throws UnsupportedOperationException if the ComparatorChain does not contain at least one Comparator + */ + @Override + public int compare(final E o1, final E o2) throws UnsupportedOperationException { + if (isLocked == false) { + checkChainIntegrity(); + isLocked = true; + } + + // iterate over all comparators in the chain + final Iterator> comparators = comparatorChain.iterator(); + for (int comparatorIndex = 0; comparators.hasNext(); ++comparatorIndex) { + + final Comparator comparator = comparators.next(); + int retval = comparator.compare(o1,o2); + if (retval != 0) { + // invert the order if it is a reverse sort + if (orderingBits.get(comparatorIndex) == true) { + if (retval > 0) { + retval = -1; + } else { + retval = 1; + } + } + return retval; + } + } + + // if comparators are exhausted, return 0 + return 0; + } + + //----------------------------------------------------------------------- + /** + * Implement a hash code for this comparator that is consistent with + * {@link #equals(Object) equals}. + * + * @return a suitable hash code + * @since 3.0 + */ + @Override + public int hashCode() { + int hash = 0; + if (null != comparatorChain) { + hash ^= comparatorChain.hashCode(); + } + if (null != orderingBits) { + hash ^= orderingBits.hashCode(); + } + return hash; + } + + /** + * Returns true iff that Object is + * is a {@link Comparator} whose ordering is known to be + * equivalent to mine. + *

          + * This implementation returns true + * iff object.{@link Object#getClass() getClass()} + * equals this.getClass(), and the underlying + * comparators and order bits are equal. + * Subclasses may want to override this behavior to remain consistent + * with the {@link Comparator#equals(Object)} contract. + * + * @param object the object to compare with + * @return true if equal + * @since 3.0 + */ + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (null == object) { + return false; + } + if (object.getClass().equals(this.getClass())) { + final ComparatorChain chain = (ComparatorChain) object; + return (null == orderingBits ? null == chain.orderingBits : orderingBits.equals(chain.orderingBits)) && + (null == comparatorChain ? null == chain.comparatorChain : + comparatorChain.equals(chain.comparatorChain)); + } + return false; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/comparators/FixedOrderComparator.java b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/FixedOrderComparator.java new file mode 100644 index 000000000..7212906e3 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/FixedOrderComparator.java @@ -0,0 +1,299 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.comparators; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A Comparator which imposes a specific order on a specific set of Objects. + * Objects are presented to the FixedOrderComparator in a specified order and + * subsequent calls to {@link #compare(Object, Object) compare} yield that order. + * For example: + *

          + * String[] planets = {"Mercury", "Venus", "Earth", "Mars"};
          + * FixedOrderComparator distanceFromSun = new FixedOrderComparator(planets);
          + * Arrays.sort(planets);                     // Sort to alphabetical order
          + * Arrays.sort(planets, distanceFromSun);    // Back to original order
          + * 
          + *

          + * Once compare has been called, the FixedOrderComparator is locked + * and attempts to modify it yield an UnsupportedOperationException. + *

          + * Instances of FixedOrderComparator are not synchronized. The class is not + * thread-safe at construction time, but it is thread-safe to perform + * multiple comparisons after all the setup operations are complete. + *

          + * This class is Serializable from Commons Collections 4.0. + * + * @since 3.0 + * @version $Id: FixedOrderComparator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class FixedOrderComparator implements Comparator, Serializable { + + /** Serialization version from Collections 4.0. */ + private static final long serialVersionUID = 82794675842863201L; + + /** + * Unknown object behavior enum. + * @since 4.0 + */ + public static enum UnknownObjectBehavior { + BEFORE, AFTER, EXCEPTION; + } + + /** Internal map of object to position */ + private final Map map = new HashMap(); + + /** Counter used in determining the position in the map */ + private int counter = 0; + + /** Is the comparator locked against further change */ + private boolean isLocked = false; + + /** The behaviour in the case of an unknown object */ + private UnknownObjectBehavior unknownObjectBehavior = UnknownObjectBehavior.EXCEPTION; + + // Constructors + //----------------------------------------------------------------------- + /** + * Constructs an empty FixedOrderComparator. + */ + public FixedOrderComparator() { + super(); + } + + /** + * Constructs a FixedOrderComparator which uses the order of the given array + * to compare the objects. + *

          + * The array is copied, so later changes will not affect the comparator. + * + * @param items the items that the comparator can compare in order + * @throws NullPointerException if the array is null + */ + public FixedOrderComparator(final T... items) { + super(); + if (items == null) { + throw new NullPointerException("The list of items must not be null"); + } + for (final T item : items) { + add(item); + } + } + + /** + * Constructs a FixedOrderComparator which uses the order of the given list + * to compare the objects. + *

          + * The list is copied, so later changes will not affect the comparator. + * + * @param items the items that the comparator can compare in order + * @throws NullPointerException if the list is null + */ + public FixedOrderComparator(final List items) { + super(); + if (items == null) { + throw new NullPointerException("The list of items must not be null"); + } + for (final T t : items) { + add(t); + } + } + + // Bean methods / state querying methods + //----------------------------------------------------------------------- + /** + * Returns true if modifications cannot be made to the FixedOrderComparator. + * FixedOrderComparators cannot be modified once they have performed a comparison. + * + * @return true if attempts to change the FixedOrderComparator yield an + * UnsupportedOperationException, false if it can be changed. + */ + public boolean isLocked() { + return isLocked; + } + + /** + * Checks to see whether the comparator is now locked against further changes. + * + * @throws UnsupportedOperationException if the comparator is locked + */ + protected void checkLocked() { + if (isLocked()) { + throw new UnsupportedOperationException("Cannot modify a FixedOrderComparator after a comparison"); + } + } + + /** + * Gets the behavior for comparing unknown objects. + * + * @return {@link UnknownObjectBehavior} + */ + public UnknownObjectBehavior getUnknownObjectBehavior() { + return unknownObjectBehavior; + } + + /** + * Sets the behavior for comparing unknown objects. + * + * @param unknownObjectBehavior the flag for unknown behaviour - + * UNKNOWN_AFTER, UNKNOWN_BEFORE or UNKNOWN_THROW_EXCEPTION + * @throws UnsupportedOperationException if a comparison has been performed + * @throws NullPointerException if unknownObjectBehavior is null + */ + public void setUnknownObjectBehavior(final UnknownObjectBehavior unknownObjectBehavior) { + checkLocked(); + if (unknownObjectBehavior == null) { + throw new NullPointerException("Unknown object behavior must not be null"); + } + this.unknownObjectBehavior = unknownObjectBehavior; + } + + // Methods for adding items + //----------------------------------------------------------------------- + /** + * Adds an item, which compares as after all items known to the Comparator. + * If the item is already known to the Comparator, its old position is + * replaced with the new position. + * + * @param obj the item to be added to the Comparator. + * @return true if obj has been added for the first time, false if + * it was already known to the Comparator. + * @throws UnsupportedOperationException if a comparison has already been made + */ + public boolean add(final T obj) { + checkLocked(); + final Integer position = map.put(obj, Integer.valueOf(counter++)); + return position == null; + } + + /** + * Adds a new item, which compares as equal to the given existing item. + * + * @param existingObj an item already in the Comparator's set of + * known objects + * @param newObj an item to be added to the Comparator's set of + * known objects + * @return true if newObj has been added for the first time, false if + * it was already known to the Comparator. + * @throws IllegalArgumentException if existingObject is not in the + * Comparator's set of known objects. + * @throws UnsupportedOperationException if a comparison has already been made + */ + public boolean addAsEqual(final T existingObj, final T newObj) { + checkLocked(); + final Integer position = map.get(existingObj); + if (position == null) { + throw new IllegalArgumentException(existingObj + " not known to " + this); + } + final Integer result = map.put(newObj, position); + return result == null; + } + + // Comparator methods + //----------------------------------------------------------------------- + /** + * Compares two objects according to the order of this Comparator. + *

          + * It is important to note that this class will throw an IllegalArgumentException + * in the case of an unrecognised object. This is not specified in the + * Comparator interface, but is the most appropriate exception. + * + * @param obj1 the first object to compare + * @param obj2 the second object to compare + * @return negative if obj1 is less, positive if greater, zero if equal + * @throws IllegalArgumentException if obj1 or obj2 are not known + * to this Comparator and an alternative behavior has not been set + * via {@link #setUnknownObjectBehavior(UnknownObjectBehavior)}. + */ + @Override + public int compare(final T obj1, final T obj2) { + isLocked = true; + final Integer position1 = map.get(obj1); + final Integer position2 = map.get(obj2); + if (position1 == null || position2 == null) { + switch (unknownObjectBehavior) { + case BEFORE: + return position1 == null ? position2 == null ? 0 : -1 : 1; + case AFTER: + return position1 == null ? position2 == null ? 0 : 1 : -1; + case EXCEPTION: + final Object unknownObj = position1 == null ? obj1 : obj2; + throw new IllegalArgumentException("Attempting to compare unknown object " + + unknownObj); + default: //could be null + throw new UnsupportedOperationException("Unknown unknownObjectBehavior: " + + unknownObjectBehavior); + } + } + return position1.compareTo(position2); + } + + //----------------------------------------------------------------------- + /** + * Implement a hash code for this comparator that is consistent with + * {@link #equals(Object) equals}. + * + * @return a hash code for this comparator. + */ + @Override + public int hashCode() { + int total = 17; + total = total*37 + (map == null ? 0 : map.hashCode()); + total = total*37 + (unknownObjectBehavior == null ? 0 : unknownObjectBehavior.hashCode()); + total = total*37 + counter; + total = total*37 + (isLocked ? 0 : 1); + return total; + } + + /** + * Returns true iff that Object is + * is a {@link Comparator} whose ordering is known to be + * equivalent to mine. + *

          + * This implementation returns true + * iff that is a {@link FixedOrderComparator} + * whose attributes are equal to mine. + * + * @param object the object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (null == object) { + return false; + } + if (object.getClass().equals(this.getClass())) { + final FixedOrderComparator comp = (FixedOrderComparator) object; + return (null == map ? null == comp.map : map.equals(comp.map)) && + (null == unknownObjectBehavior ? null == comp.unknownObjectBehavior : + unknownObjectBehavior == comp.unknownObjectBehavior && + counter == comp.counter && + isLocked == comp.isLocked && + unknownObjectBehavior == comp.unknownObjectBehavior); + } + return false; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/comparators/NullComparator.java b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/NullComparator.java new file mode 100644 index 000000000..15093ec8e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/NullComparator.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.comparators; + +import java.io.Serializable; +import java.util.Comparator; + +import org.apache.commons.collections4.ComparatorUtils; + +/** + * A Comparator that will compare nulls to be either lower or higher than + * other objects. + * + * @since 2.0 + * @version $Id: NullComparator.java 1683951 2015-06-06 20:19:03Z tn $ + */ +public class NullComparator implements Comparator, Serializable { + + /** Serialization version. */ + private static final long serialVersionUID = -5820772575483504339L; + + /** + * The comparator to use when comparing two non-null objects. + **/ + private final Comparator nonNullComparator; + + /** + * Specifies whether a null are compared as higher than + * non-null objects. + **/ + private final boolean nullsAreHigh; + + //----------------------------------------------------------------------- + /** + * Construct an instance that sorts null higher than any + * non-null object it is compared with. When comparing two + * non-null objects, the {@link ComparableComparator} is + * used. + **/ + @SuppressWarnings("unchecked") + public NullComparator() { + this(ComparatorUtils.NATURAL_COMPARATOR, true); + } + + /** + * Construct an instance that sorts null higher than any + * non-null object it is compared with. When comparing two + * non-null objects, the specified {@link Comparator} is + * used. + * + * @param nonNullComparator the comparator to use when comparing two + * non-null objects. This argument cannot be + * null + * + * @exception NullPointerException if nonNullComparator is + * null + **/ + public NullComparator(final Comparator nonNullComparator) { + this(nonNullComparator, true); + } + + /** + * Construct an instance that sorts null higher or lower than + * any non-null object it is compared with. When comparing + * two non-null objects, the {@link ComparableComparator} is + * used. + * + * @param nullsAreHigh a true value indicates that + * null should be compared as higher than a + * non-null object. A false value indicates + * that null should be compared as lower than a + * non-null object. + **/ + @SuppressWarnings("unchecked") + public NullComparator(final boolean nullsAreHigh) { + this(ComparatorUtils.NATURAL_COMPARATOR, nullsAreHigh); + } + + /** + * Construct an instance that sorts null higher or lower than + * any non-null object it is compared with. When comparing + * two non-null objects, the specified {@link Comparator} is + * used. + * + * @param nonNullComparator the comparator to use when comparing two + * non-null objects. This argument cannot be + * null + * + * @param nullsAreHigh a true value indicates that + * null should be compared as higher than a + * non-null object. A false value indicates + * that null should be compared as lower than a + * non-null object. + * + * @exception NullPointerException if nonNullComparator is + * null + **/ + public NullComparator(final Comparator nonNullComparator, final boolean nullsAreHigh) { + this.nonNullComparator = nonNullComparator; + this.nullsAreHigh = nullsAreHigh; + + if (nonNullComparator == null) { + throw new NullPointerException("null nonNullComparator"); + } + } + + //----------------------------------------------------------------------- + /** + * Perform a comparison between two objects. If both objects are + * null, a 0 value is returned. If one object + * is null and the other is not, the result is determined on + * whether the Comparator was constructed to have nulls as higher or lower + * than other objects. If neither object is null, an + * underlying comparator specified in the constructor (or the default) is + * used to compare the non-null objects. + * + * @param o1 the first object to compare + * @param o2 the object to compare it to. + * @return -1 if o1 is "lower" than (less than, + * before, etc.) o2; 1 if o1 is + * "higher" than (greater than, after, etc.) o2; or + * 0 if o1 and o2 are equal. + **/ + @Override + public int compare(final E o1, final E o2) { + if(o1 == o2) { return 0; } + if(o1 == null) { return this.nullsAreHigh ? 1 : -1; } + if(o2 == null) { return this.nullsAreHigh ? -1 : 1; } + return this.nonNullComparator.compare(o1, o2); + } + + //----------------------------------------------------------------------- + /** + * Implement a hash code for this comparator that is consistent with + * {@link #equals(Object)}. + * + * @return a hash code for this comparator. + **/ + @Override + public int hashCode() { + return (nullsAreHigh ? -1 : 1) * nonNullComparator.hashCode(); + } + + /** + * Determines whether the specified object represents a comparator that is + * equal to this comparator. + * + * @param obj the object to compare this comparator with. + * + * @return true if the specified object is a NullComparator + * with equivalent null comparison behavior + * (i.e. null high or low) and with equivalent underlying + * non-null object comparators. + **/ + @Override + public boolean equals(final Object obj) { + if(obj == null) { return false; } + if(obj == this) { return true; } + if(!obj.getClass().equals(this.getClass())) { return false; } + + final NullComparator other = (NullComparator) obj; + + return this.nullsAreHigh == other.nullsAreHigh && + this.nonNullComparator.equals(other.nonNullComparator); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/comparators/ReverseComparator.java b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/ReverseComparator.java new file mode 100644 index 000000000..4ea056bd9 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/ReverseComparator.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.comparators; + +import java.io.Serializable; +import java.util.Comparator; + +import org.apache.commons.collections4.ComparatorUtils; + +/** + * Reverses the order of another comparator by reversing the arguments + * to its {@link #compare(Object, Object) compare} method. + * + * @since 2.0 + * @version $Id: ReverseComparator.java 1683951 2015-06-06 20:19:03Z tn $ + * + * @see java.util.Collections#reverseOrder() + */ +public class ReverseComparator implements Comparator, Serializable { + + /** Serialization version from Collections 2.0. */ + private static final long serialVersionUID = 2858887242028539265L; + + /** The comparator being decorated. */ + private final Comparator comparator; + + //----------------------------------------------------------------------- + /** + * Creates a comparator that compares objects based on the inverse of their + * natural ordering. Using this Constructor will create a ReverseComparator + * that is functionally identical to the Comparator returned by + * java.util.Collections.reverseOrder(). + * + * @see java.util.Collections#reverseOrder() + */ + public ReverseComparator() { + this(null); + } + + /** + * Creates a comparator that inverts the comparison + * of the given comparator. If you pass in null, + * the ReverseComparator defaults to reversing the + * natural order, as per {@link java.util.Collections#reverseOrder()}. + * + * @param comparator Comparator to reverse + */ + @SuppressWarnings("unchecked") + public ReverseComparator(final Comparator comparator) { + this.comparator = comparator == null ? ComparatorUtils.NATURAL_COMPARATOR : comparator; + } + + //----------------------------------------------------------------------- + /** + * Compares two objects in reverse order. + * + * @param obj1 the first object to compare + * @param obj2 the second object to compare + * @return negative if obj1 is less, positive if greater, zero if equal + */ + @Override + public int compare(final E obj1, final E obj2) { + return comparator.compare(obj2, obj1); + } + + //----------------------------------------------------------------------- + /** + * Implement a hash code for this comparator that is consistent with + * {@link #equals(Object) equals}. + * + * @return a suitable hash code + * @since 3.0 + */ + @Override + public int hashCode() { + return "ReverseComparator".hashCode() ^ comparator.hashCode(); + } + + /** + * Returns true iff that Object is + * is a {@link Comparator} whose ordering is known to be + * equivalent to mine. + *

          + * This implementation returns true + * iff object.{@link Object#getClass() getClass()} + * equals this.getClass(), and the underlying + * comparators are equal. + * Subclasses may want to override this behavior to remain consistent + * with the {@link Comparator#equals(Object) equals} contract. + * + * @param object the object to compare to + * @return true if equal + * @since 3.0 + */ + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (null == object) { + return false; + } + if (object.getClass().equals(this.getClass())) { + final ReverseComparator thatrc = (ReverseComparator) object; + return comparator.equals(thatrc.comparator); + } + return false; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/comparators/TransformingComparator.java b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/TransformingComparator.java new file mode 100644 index 000000000..fc3443575 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/TransformingComparator.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.comparators; + +import java.io.Serializable; +import java.util.Comparator; + +import org.apache.commons.collections4.ComparatorUtils; +import org.apache.commons.collections4.Transformer; + +/** + * Decorates another Comparator with transformation behavior. That is, the + * return value from the transform operation will be passed to the decorated + * {@link Comparator#compare(Object,Object) compare} method. + *

          + * This class is Serializable from Commons Collections 4.0. + * + * @since 2.1 + * @version $Id: TransformingComparator.java 1683951 2015-06-06 20:19:03Z tn $ + * + * @see org.apache.commons.collections4.Transformer + * @see org.apache.commons.collections4.comparators.ComparableComparator + */ +public class TransformingComparator implements Comparator, Serializable { + + /** Serialization version from Collections 4.0. */ + private static final long serialVersionUID = 3456940356043606220L; + + /** The decorated comparator. */ + private final Comparator decorated; + /** The transformer being used. */ + private final Transformer transformer; + + //----------------------------------------------------------------------- + /** + * Constructs an instance with the given Transformer and a + * {@link ComparableComparator ComparableComparator}. + * + * @param transformer what will transform the arguments to compare + */ + @SuppressWarnings("unchecked") + public TransformingComparator(final Transformer transformer) { + this(transformer, ComparatorUtils.NATURAL_COMPARATOR); + } + + /** + * Constructs an instance with the given Transformer and Comparator. + * + * @param transformer what will transform the arguments to compare + * @param decorated the decorated Comparator + */ + public TransformingComparator(final Transformer transformer, + final Comparator decorated) { + this.decorated = decorated; + this.transformer = transformer; + } + + //----------------------------------------------------------------------- + /** + * Returns the result of comparing the values from the transform operation. + * + * @param obj1 the first object to transform then compare + * @param obj2 the second object to transform then compare + * @return negative if obj1 is less, positive if greater, zero if equal + */ + @Override + public int compare(final I obj1, final I obj2) { + final O value1 = this.transformer.transform(obj1); + final O value2 = this.transformer.transform(obj2); + return this.decorated.compare(value1, value2); + } + + //----------------------------------------------------------------------- + /** + * Implement a hash code for this comparator that is consistent with + * {@link #equals(Object) equals}. + * + * @return a hash code for this comparator. + */ + @Override + public int hashCode() { + int total = 17; + total = total*37 + (decorated == null ? 0 : decorated.hashCode()); + total = total*37 + (transformer == null ? 0 : transformer.hashCode()); + return total; + } + + /** + * Returns true iff that Object is + * is a {@link Comparator} whose ordering is known to be + * equivalent to mine. + *

          + * This implementation returns true + * iff that is a {@link TransformingComparator} + * whose attributes are equal to mine. + * + * @param object the object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (null == object) { + return false; + } + if (object.getClass().equals(this.getClass())) { + final TransformingComparator comp = (TransformingComparator) object; + return (null == decorated ? null == comp.decorated : decorated.equals(comp.decorated)) && + (null == transformer ? null == comp.transformer : transformer.equals(comp.transformer)); + } + return false; + } + +} + diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/comparators/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/package-info.java new file mode 100644 index 000000000..c1e554a9f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/comparators/package-info.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the + * {@link java.util.Comparator Comparator} interface. + *

          + * You may also consider using + * {@link org.apache.commons.collections4.ComparatorUtils ComparatorUtils}, + * which is a single class that uses static methods to construct instances + * of the classes in this package. + * + * @version $Id: package-info.java 1477747 2013-04-30 18:16:48Z tn $ + */ +package org.apache.commons.collections4.comparators; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/AbstractQuantifierPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/AbstractQuantifierPredicate.java new file mode 100644 index 000000000..70e5fd8f9 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/AbstractQuantifierPredicate.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Abstract base class for quantification predicates, e.g. All, Any, None. + * + * @since 4.0 + * @version $Id: AbstractQuantifierPredicate.java 1543167 2013-11-18 21:21:32Z ggregory $ + */ +public abstract class AbstractQuantifierPredicate implements PredicateDecorator, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -3094696765038308799L; + + /** The array of predicates to call */ + protected final Predicate[] iPredicates; + + /** + * Constructor that performs no validation. + * + * @param predicates the predicates to check, not cloned, not null + */ + public AbstractQuantifierPredicate(final Predicate... predicates) { + iPredicates = predicates; + } + + /** + * Gets the predicates. + * + * @return a copy of the predicates + * @since 3.1 + */ + public Predicate[] getPredicates() { + return FunctorUtils.copy(iPredicates); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/AllPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/AllPredicate.java new file mode 100644 index 000000000..2d1455c15 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/AllPredicate.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import static org.apache.commons.collections4.functors.FunctorUtils.coerce; +import static org.apache.commons.collections4.functors.FunctorUtils.validate; +import static org.apache.commons.collections4.functors.TruePredicate.truePredicate; + +import java.util.Collection; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if all the + * predicates return true. + * If the array of predicates is empty, then this predicate returns true. + *

          + * NOTE: In versions prior to 3.2 an array size of zero or one + * threw an exception. + * + * @since 3.0 + * @version $Id: AllPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class AllPredicate extends AbstractQuantifierPredicate { + + /** Serial version UID */ + private static final long serialVersionUID = -3094696765038308799L; + + /** + * Factory to create the predicate. + *

          + * If the array is size zero, the predicate always returns true. + * If the array is size one, then that predicate is returned. + * + * @param the type that the predicate queries + * @param predicates the predicates to check, cloned, not null + * @return the all predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + */ + public static Predicate allPredicate(final Predicate... predicates) { + FunctorUtils.validate(predicates); + if (predicates.length == 0) { + return truePredicate(); + } + if (predicates.length == 1) { + return coerce(predicates[0]); + } + + return new AllPredicate(FunctorUtils.copy(predicates)); + } + + /** + * Factory to create the predicate. + *

          + * If the collection is size zero, the predicate always returns true. + * If the collection is size one, then that predicate is returned. + * + * @param the type that the predicate queries + * @param predicates the predicates to check, cloned, not null + * @return the all predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + */ + public static Predicate allPredicate(final Collection> predicates) { + final Predicate[] preds = validate(predicates); + if (preds.length == 0) { + return truePredicate(); + } + if (preds.length == 1) { + return coerce(preds[0]); + } + return new AllPredicate(preds); + } + + /** + * Constructor that performs no validation. + * Use allPredicate if you want that. + * + * @param predicates the predicates to check, not cloned, not null + */ + public AllPredicate(final Predicate... predicates) { + super(predicates); + } + + /** + * Evaluates the predicate returning true if all predicates return true. + * + * @param object the input object + * @return true if all decorated predicates return true + */ + public boolean evaluate(final T object) { + for (final Predicate iPredicate : iPredicates) { + if (!iPredicate.evaluate(object)) { + return false; + } + } + return true; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/AndPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/AndPredicate.java new file mode 100644 index 000000000..1ef0ec904 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/AndPredicate.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if both the predicates return true. + * + * @since 3.0 + * @version $Id: AndPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class AndPredicate implements PredicateDecorator, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 4189014213763186912L; + + /** The array of predicates to call */ + private final Predicate iPredicate1; + /** The array of predicates to call */ + private final Predicate iPredicate2; + + /** + * Factory to create the predicate. + * + * @param the type that the predicate queries + * @param predicate1 the first predicate to check, not null + * @param predicate2 the second predicate to check, not null + * @return the and predicate + * @throws NullPointerException if either predicate is null + */ + public static Predicate andPredicate(final Predicate predicate1, + final Predicate predicate2) { + if (predicate1 == null || predicate2 == null) { + throw new NullPointerException("Predicate must not be null"); + } + return new AndPredicate(predicate1, predicate2); + } + + /** + * Constructor that performs no validation. + * Use andPredicate if you want that. + * + * @param predicate1 the first predicate to check, not null + * @param predicate2 the second predicate to check, not null + */ + public AndPredicate(final Predicate predicate1, final Predicate predicate2) { + super(); + iPredicate1 = predicate1; + iPredicate2 = predicate2; + } + + /** + * Evaluates the predicate returning true if both predicates return true. + * + * @param object the input object + * @return true if both decorated predicates return true + */ + public boolean evaluate(final T object) { + return iPredicate1.evaluate(object) && iPredicate2.evaluate(object); + } + + /** + * Gets the two predicates being decorated as an array. + * + * @return the predicates + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public Predicate[] getPredicates() { + return new Predicate[] {iPredicate1, iPredicate2}; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/AnyPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/AnyPredicate.java new file mode 100644 index 000000000..90973034e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/AnyPredicate.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.util.Collection; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if any of the + * predicates return true. + * If the array of predicates is empty, then this predicate returns false. + *

          + * NOTE: In versions prior to 3.2 an array size of zero or one + * threw an exception. + * + * @since 3.0 + * @version $Id: AnyPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class AnyPredicate extends AbstractQuantifierPredicate { + + /** Serial version UID */ + private static final long serialVersionUID = 7429999530934647542L; + + /** + * Factory to create the predicate. + *

          + * If the array is size zero, the predicate always returns false. + * If the array is size one, then that predicate is returned. + * + * @param the type that the predicate queries + * @param predicates the predicates to check, cloned, not null + * @return the any predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + */ + @SuppressWarnings("unchecked") + public static Predicate anyPredicate(final Predicate... predicates) { + FunctorUtils.validate(predicates); + if (predicates.length == 0) { + return FalsePredicate.falsePredicate(); + } + if (predicates.length == 1) { + return (Predicate) predicates[0]; + } + return new AnyPredicate(FunctorUtils.copy(predicates)); + } + + /** + * Factory to create the predicate. + *

          + * If the collection is size zero, the predicate always returns false. + * If the collection is size one, then that predicate is returned. + * + * @param the type that the predicate queries + * @param predicates the predicates to check, cloned, not null + * @return the all predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + */ + @SuppressWarnings("unchecked") + public static Predicate anyPredicate(final Collection> predicates) { + final Predicate[] preds = FunctorUtils.validate(predicates); + if (preds.length == 0) { + return FalsePredicate.falsePredicate(); + } + if (preds.length == 1) { + return (Predicate) preds[0]; + } + return new AnyPredicate(preds); + } + + /** + * Constructor that performs no validation. + * Use anyPredicate if you want that. + * + * @param predicates the predicates to check, not cloned, not null + */ + public AnyPredicate(final Predicate... predicates) { + super(predicates); + } + + /** + * Evaluates the predicate returning true if any predicate returns true. + * + * @param object the input object + * @return true if any decorated predicate return true + */ + public boolean evaluate(final T object) { + for (final Predicate iPredicate : iPredicates) { + if (iPredicate.evaluate(object)) { + return true; + } + } + return false; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/CatchAndRethrowClosure.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/CatchAndRethrowClosure.java new file mode 100644 index 000000000..5fb324041 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/CatchAndRethrowClosure.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import org.apache.commons.collections4.Closure; +import org.apache.commons.collections4.FunctorException; + +/** + * {@link Closure} that catches any checked exception and re-throws it as a + * {@link FunctorException} runtime exception. Example usage: + * + *

          + * // Create a catch and re-throw closure via anonymous subclass
          + * CatchAndRethrowClosure<String> writer = new ThrowingClosure() {
          + *     private java.io.Writer out = // some writer
          + *
          + *     protected void executeAndThrow(String input) throws IOException {
          + *         out.write(input); // throwing of IOException allowed
          + *     }
          + * };
          + *
          + * // use catch and re-throw closure
          + * java.util.List strList = // some list
          + * try {
          + *     CollctionUtils.forAllDo(strList, writer);
          + * } catch (FunctorException ex) {
          + *     Throwable originalError = ex.getCause();
          + *     // handle error
          + * }
          + * 
          + * + * @since 4.0 + * @version $Id: CatchAndRethrowClosure.java 1477798 2013-04-30 19:49:02Z tn $ + */ +public abstract class CatchAndRethrowClosure implements Closure { + + /** + * Execute this closure on the specified input object. + * + * @param input the input to execute on + * @throws FunctorException (runtime) if the closure execution resulted in a + * checked exception. + */ + public void execute(final E input) { + try { + executeAndThrow(input); + } catch (final RuntimeException ex) { + throw ex; + } catch (final Throwable t) { + throw new FunctorException(t); + } + } + + /** + * Execute this closure on the specified input object. + * + * @param input the input to execute on + * @throws Throwable if the closure execution resulted in a checked + * exception. + */ + protected abstract void executeAndThrow(E input) throws Throwable; +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ChainedClosure.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ChainedClosure.java new file mode 100644 index 000000000..f6333d47b --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ChainedClosure.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; +import java.util.Collection; + +import org.apache.commons.collections4.Closure; + +/** + * Closure implementation that chains the specified closures together. + * + * @since 3.0 + * @version $Id: ChainedClosure.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class ChainedClosure implements Closure, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -3520677225766901240L; + + /** The closures to call in turn */ + private final Closure[] iClosures; + + /** + * Factory method that performs validation and copies the parameter array. + * + * @param the type that the closure acts on + * @param closures the closures to chain, copied, no nulls + * @return the chained closure + * @throws NullPointerException if the closures array is null + * @throws NullPointerException if any closure in the array is null + */ + public static Closure chainedClosure(final Closure... closures) { + FunctorUtils.validate(closures); + if (closures.length == 0) { + return NOPClosure.nopClosure(); + } + return new ChainedClosure(closures); + } + + /** + * Create a new Closure that calls each closure in turn, passing the + * result into the next closure. The ordering is that of the iterator() + * method on the collection. + * + * @param the type that the closure acts on + * @param closures a collection of closures to chain + * @return the chained closure + * @throws NullPointerException if the closures collection is null + * @throws NullPointerException if any closure in the collection is null + */ + @SuppressWarnings("unchecked") + public static Closure chainedClosure(final Collection> closures) { + if (closures == null) { + throw new NullPointerException("Closure collection must not be null"); + } + if (closures.size() == 0) { + return NOPClosure.nopClosure(); + } + // convert to array like this to guarantee iterator() ordering + final Closure[] cmds = new Closure[closures.size()]; + int i = 0; + for (final Closure closure : closures) { + cmds[i++] = closure; + } + FunctorUtils.validate(cmds); + return new ChainedClosure(false, cmds); + } + + /** + * Hidden constructor for the use by the static factory methods. + * + * @param clone if {@code true} the input argument will be cloned + * @param closures the closures to chain, no nulls + */ + private ChainedClosure(final boolean clone, final Closure... closures) { + super(); + iClosures = clone ? FunctorUtils.copy(closures) : closures; + } + + /** + * Constructor that performs no validation. + * Use chainedClosure if you want that. + * + * @param closures the closures to chain, copied, no nulls + */ + public ChainedClosure(final Closure... closures) { + this(true, closures); + } + + /** + * Execute a list of closures. + * + * @param input the input object passed to each closure + */ + public void execute(final E input) { + for (final Closure iClosure : iClosures) { + iClosure.execute(input); + } + } + + /** + * Gets the closures. + * + * @return a copy of the closures + * @since 3.1 + */ + public Closure[] getClosures() { + return FunctorUtils.copy(iClosures); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ChainedTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ChainedTransformer.java new file mode 100644 index 000000000..a5bdba1db --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ChainedTransformer.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; +import java.util.Collection; + +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that chains the specified transformers together. + *

          + * The input object is passed to the first transformer. The transformed result + * is passed to the second transformer and so on. + * + * @since 3.0 + * @version $Id: ChainedTransformer.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class ChainedTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 3514945074733160196L; + + /** The transformers to call in turn */ + private final Transformer[] iTransformers; + + /** + * Factory method that performs validation and copies the parameter array. + * + * @param the object type + * @param transformers the transformers to chain, copied, no nulls + * @return the chained transformer + * @throws NullPointerException if the transformers array is null + * @throws NullPointerException if any transformer in the array is null + */ + public static Transformer chainedTransformer(final Transformer... transformers) { + FunctorUtils.validate(transformers); + if (transformers.length == 0) { + return NOPTransformer.nopTransformer(); + } + return new ChainedTransformer(transformers); + } + + /** + * Create a new Transformer that calls each transformer in turn, passing the + * result into the next transformer. The ordering is that of the iterator() + * method on the collection. + * + * @param the object type + * @param transformers a collection of transformers to chain + * @return the chained transformer + * @throws NullPointerException if the transformers collection is null + * @throws NullPointerException if any transformer in the collection is null + */ + @SuppressWarnings("unchecked") + public static Transformer chainedTransformer( + final Collection> transformers) { + if (transformers == null) { + throw new NullPointerException("Transformer collection must not be null"); + } + if (transformers.size() == 0) { + return NOPTransformer.nopTransformer(); + } + // convert to array like this to guarantee iterator() ordering + final Transformer[] cmds = transformers.toArray(new Transformer[transformers.size()]); + FunctorUtils.validate(cmds); + return new ChainedTransformer(false, cmds); + } + + /** + * Hidden constructor for the use by the static factory methods. + * + * @param clone if {@code true} the input argument will be cloned + * @param transformers the transformers to chain, no nulls + */ + private ChainedTransformer(final boolean clone, final Transformer[] transformers) { + super(); + iTransformers = clone ? FunctorUtils.copy(transformers) : transformers; + } + + /** + * Constructor that performs no validation. + * Use chainedTransformer if you want that. + * + * @param transformers the transformers to chain, copied, no nulls + */ + public ChainedTransformer(final Transformer... transformers) { + this(true, transformers); + } + + /** + * Transforms the input to result via each decorated transformer + * + * @param object the input object passed to the first transformer + * @return the transformed result + */ + public T transform(T object) { + for (final Transformer iTransformer : iTransformers) { + object = iTransformer.transform(object); + } + return object; + } + + /** + * Gets the transformers. + * + * @return a copy of the transformers + * @since 3.1 + */ + public Transformer[] getTransformers() { + return FunctorUtils.copy(iTransformers); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/CloneTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/CloneTransformer.java new file mode 100644 index 000000000..783f36e5f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/CloneTransformer.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that returns a clone of the input object. + *

          + * Clone is performed using PrototypeFactory.prototypeFactory(input).create(). + *

          + * WARNING: from v4.1 onwards this class will not be serializable anymore + * in order to prevent potential remote code execution exploits. Please refer to + * COLLECTIONS-580 + * for more details. + * + * @since 3.0 + * @version $Id: CloneTransformer.java 1714262 2015-11-13 20:08:45Z tn $ + */ +public class CloneTransformer implements Transformer { + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") // the singleton instance works for all types + public static final Transformer INSTANCE = new CloneTransformer(); + + /** + * Factory returning the singleton instance. + * + * @param the type of the objects to be cloned + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") // the singleton instance works for all types + public static Transformer cloneTransformer() { + return INSTANCE; + } + + /** + * Constructor. + */ + private CloneTransformer() { + super(); + } + + /** + * Transforms the input to result by cloning it. + * + * @param input the input object to transform + * @return the transformed result + */ + @Override + public T transform(final T input) { + if (input == null) { + return null; + } + return PrototypeFactory.prototypeFactory(input).create(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ClosureTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ClosureTransformer.java new file mode 100644 index 000000000..5f4fa31ca --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ClosureTransformer.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Closure; +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that calls a Closure using the input object + * and then returns the input. + * + * @since 3.0 + * @version $Id: ClosureTransformer.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class ClosureTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 478466901448617286L; + + /** The closure to wrap */ + private final Closure iClosure; + + /** + * Factory method that performs validation. + * + * @param the type of the object to transform + * @param closure the closure to call, not null + * @return the closure transformer + * @throws NullPointerException if the closure is null + */ + public static Transformer closureTransformer(final Closure closure) { + if (closure == null) { + throw new NullPointerException("Closure must not be null"); + } + return new ClosureTransformer(closure); + } + + /** + * Constructor that performs no validation. + * Use closureTransformer if you want that. + * + * @param closure the closure to call, not null + */ + public ClosureTransformer(final Closure closure) { + super(); + iClosure = closure; + } + + /** + * Transforms the input to result by executing a closure. + * + * @param input the input object to transform + * @return the transformed result + */ + public T transform(final T input) { + iClosure.execute(input); + return input; + } + + /** + * Gets the closure. + * + * @return the closure + * @since 3.1 + */ + public Closure getClosure() { + return iClosure; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ComparatorPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ComparatorPredicate.java new file mode 100644 index 000000000..8f9819320 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ComparatorPredicate.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; +import java.util.Comparator; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate that compares the input object with the one stored in the predicate using a comparator. + * In addition, the comparator result can be evaluated in accordance to a supplied criterion value. + * + * In order to demonstrate the use of the predicate, the following variables are declared: + * + *
          + * Integer ONE = Integer.valueOf(1);
          + * Integer TWO = Integer.valueOf(2);
          + *
          + * Comparator comparator = new Comparator() {
          + *
          + *     public int compare(Object first, Object second) {
          + *         return ((Integer) second) - ((Integer) first);
          + *     }
          + *
          + * };
          + * 
          + * + * Using the declared variables, the ComparatorPredicate can be used used in the + * following way: + * + *
          + * ComparatorPredicate.comparatorPredicate(ONE, comparator).evaluate(TWO);
          + * 
          + * + * The input variable TWO in compared to the stored variable ONE using + * the supplied comparator. This is the default usage of the predicate and will return + * true if the underlying comparator returns 0. In addition to the default + * usage of the predicate, it is possible to evaluate the comparator's result in several ways. The + * following {@link Criterion} enumeration values are provided by the predicate: + *

          + * + *
            + *
          • EQUAL
          • + *
          • GREATER
          • + *
          • GREATER_OR_EQUAL
          • + *
          • LESS
          • + *
          • LESS_OR_EQUAL
          • + *
          + * + * The following examples demonstrates how these constants can be used in order to manipulate the + * evaluation of a comparator result. + * + *
          + * ComparatorPredicate.comparatorPredicate(ONE, comparator,ComparatorPredicate.Criterion.GREATER).evaluate(TWO);
          + * 
          + * + * The input variable TWO is compared to the stored variable ONE using the supplied comparator + * using the GREATER evaluation criterion constant. This instructs the predicate to + * return true if the comparator returns a value greater than 0. + * + * @since 4.0 + * @version $Id: ComparatorPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class ComparatorPredicate implements Predicate, Serializable { + + private static final long serialVersionUID = -1863209236504077399L; + + public enum Criterion { + EQUAL, GREATER, LESS, GREATER_OR_EQUAL, LESS_OR_EQUAL, + } + + // Instance variables: + + /** The internal object to compare with */ + private final T object; + + /** The comparator to use for comparison */ + private final Comparator comparator; + + /** The comparison evaluation criterion to use */ + private final Criterion criterion; + + /** + * Factory to create the comparator predicate + * + * @param the type that the predicate queries + * @param object the object to compare to + * @param comparator the comparator to use for comparison + * @return the predicate + * @throws NullPointerException if comparator is null + */ + public static Predicate comparatorPredicate(final T object, final Comparator comparator) { + return comparatorPredicate(object, comparator, Criterion.EQUAL); + } + + /** + * Factory to create the comparator predicate + * + * @param the type that the predicate queries + * @param object the object to compare to + * @param comparator the comparator to use for comparison + * @param criterion the criterion to use to evaluate comparison + * @return the predicate + * @throws NullPointerException if comparator or criterion is null + */ + public static Predicate comparatorPredicate(final T object, final Comparator comparator, + final Criterion criterion) { + if (comparator == null) { + throw new NullPointerException("Comparator must not be null."); + } + if (criterion == null) { + throw new NullPointerException("Criterion must not be null."); + } + return new ComparatorPredicate(object, comparator, criterion); + } + + /** + * Constructor that performs no validation. + * Use comparatorPredicate if you want that. + * + * @param object the object to compare to + * @param comparator the comparator to use for comparison + * @param criterion the criterion to use to evaluate comparison + */ + public ComparatorPredicate(final T object, final Comparator comparator, final Criterion criterion) { + super(); + this.object = object; + this.comparator = comparator; + this.criterion = criterion; + } + + /** + * Evaluates the predicate. The predicate evaluates to true in the following cases: + * + *
            + *
          • comparator.compare(object, input) == 0 && criterion == EQUAL
          • + *
          • comparator.compare(object, input) < 0 && criterion == LESS
          • + *
          • comparator.compare(object, input) > 0 && criterion == GREATER
          • + *
          • comparator.compare(object, input) >= 0 && criterion == GREATER_OR_EQUAL
          • + *
          • comparator.compare(object, input) <= 0 && criterion == LESS_OR_EQUAL
          • + *
          + * + * @see org.apache.commons.collections4.Predicate#evaluate(java.lang.Object) + * @see java.util.Comparator#compare(java.lang.Object first, java.lang.Object second) + * + * @param target the target object to compare to + * @return {@code true} if the comparison succeeds according to the selected criterion + * @throws IllegalStateException if the criterion is invalid (really not possible) + */ + public boolean evaluate(final T target) { + + boolean result = false; + final int comparison = comparator.compare(object, target); + switch (criterion) { + case EQUAL: + result = comparison == 0; + break; + case GREATER: + result = comparison > 0; + break; + case LESS: + result = comparison < 0; + break; + case GREATER_OR_EQUAL: + result = comparison >= 0; + break; + case LESS_OR_EQUAL: + result = comparison <= 0; + break; + default: + throw new IllegalStateException("The current criterion '" + criterion + "' is invalid."); + } + + return result; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ConstantFactory.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ConstantFactory.java new file mode 100644 index 000000000..d38ce3144 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ConstantFactory.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Factory; + +/** + * Factory implementation that returns the same constant each time. + *

          + * No check is made that the object is immutable. In general, only immutable + * objects should use the constant factory. Mutable objects should + * use the prototype factory. + * + * @since 3.0 + * @version $Id: ConstantFactory.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public class ConstantFactory implements Factory, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -3520677225766901240L; + + /** Returns null each time */ + @SuppressWarnings("rawtypes") // The null factory works for all object types + public static final Factory NULL_INSTANCE = new ConstantFactory(null); + + /** The closures to call in turn */ + private final T iConstant; + + /** + * Factory method that performs validation. + * + * @param the type of the constant + * @param constantToReturn the constant object to return each time in the factory + * @return the constant factory. + */ + @SuppressWarnings("unchecked") // The null factory works for all object types + public static Factory constantFactory(final T constantToReturn) { + if (constantToReturn == null) { + return (Factory) NULL_INSTANCE; + } + return new ConstantFactory(constantToReturn); + } + + /** + * Constructor that performs no validation. + * Use constantFactory if you want that. + * + * @param constantToReturn the constant to return each time + */ + public ConstantFactory(final T constantToReturn) { + super(); + iConstant = constantToReturn; + } + + /** + * Always return constant. + * + * @return the stored constant value + */ + public T create() { + return iConstant; + } + + /** + * Gets the constant. + * + * @return the constant + * @since 3.1 + */ + public T getConstant() { + return iConstant; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ConstantTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ConstantTransformer.java new file mode 100644 index 000000000..b740c9260 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ConstantTransformer.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that returns the same constant each time. + *

          + * No check is made that the object is immutable. In general, only immutable + * objects should use the constant factory. Mutable objects should + * use the prototype factory. + * + * @since 3.0 + * @version $Id: ConstantTransformer.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public class ConstantTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 6374440726369055124L; + + /** Returns null each time */ + @SuppressWarnings("rawtypes") + public static final Transformer NULL_INSTANCE = new ConstantTransformer(null); + + /** The closures to call in turn */ + private final O iConstant; + + /** + * Get a typed null instance. + * + * @param the input type + * @param the output type + * @return Transformer that always returns null. + */ + @SuppressWarnings("unchecked") // The null transformer works for all object types + public static Transformer nullTransformer() { + return (Transformer) NULL_INSTANCE; + } + + /** + * Transformer method that performs validation. + * + * @param the input type + * @param the output type + * @param constantToReturn the constant object to return each time in the factory + * @return the constant factory. + */ + public static Transformer constantTransformer(final O constantToReturn) { + if (constantToReturn == null) { + return nullTransformer(); + } + return new ConstantTransformer(constantToReturn); + } + + /** + * Constructor that performs no validation. + * Use constantTransformer if you want that. + * + * @param constantToReturn the constant to return each time + */ + public ConstantTransformer(final O constantToReturn) { + super(); + iConstant = constantToReturn; + } + + /** + * Transforms the input by ignoring it and returning the stored constant instead. + * + * @param input the input object which is ignored + * @return the stored constant + */ + public O transform(final I input) { + return iConstant; + } + + /** + * Gets the constant. + * + * @return the constant + * @since 3.1 + */ + public O getConstant() { + return iConstant; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof ConstantTransformer == false) { + return false; + } + final Object otherConstant = ((ConstantTransformer) obj).getConstant(); + return otherConstant == getConstant() || otherConstant != null && otherConstant.equals(getConstant()); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int result = "ConstantTransformer".hashCode() << 2; + if (getConstant() != null) { + result |= getConstant().hashCode(); + } + return result; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/DefaultEquator.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/DefaultEquator.java new file mode 100644 index 000000000..a27e2784c --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/DefaultEquator.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Equator; + +/** + * Default {@link Equator} implementation. + * + * @param the types of object this {@link Equator} can evaluate. + * @since 4.0 + * @version $Id: DefaultEquator.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public class DefaultEquator implements Equator, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 825802648423525485L; + + /** Static instance */ + @SuppressWarnings("rawtypes") // the static instance works for all types + public static final DefaultEquator INSTANCE = new DefaultEquator(); + + /** + * Hashcode used for null objects. + */ + public static final int HASHCODE_NULL = -1; + + /** + * Factory returning the typed singleton instance. + * + * @param the object type + * @return the singleton instance + */ + @SuppressWarnings("unchecked") // the static instance works for all types + public static DefaultEquator defaultEquator() { + return (DefaultEquator) DefaultEquator.INSTANCE; + } + + /** + * Restricted constructor. + */ + private DefaultEquator() { + super(); + } + + /** + * {@inheritDoc} Delegates to {@link Object#equals(Object)}. + */ + public boolean equate(final T o1, final T o2) { + return o1 == o2 || o1 != null && o1.equals(o2); + } + + /** + * {@inheritDoc} + * + * @return o.hashCode() if o is non- + * null, else {@link #HASHCODE_NULL}. + */ + public int hash(final T o) { + return o == null ? HASHCODE_NULL : o.hashCode(); + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/EqualPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/EqualPredicate.java new file mode 100644 index 000000000..761eb7695 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/EqualPredicate.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Equator; +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if the input is the same object + * as the one stored in this predicate by equals. + * + * @since 3.0 + * @version $Id: EqualPredicate.java 1683576 2015-06-04 15:23:56Z tn $ + */ +public final class EqualPredicate implements Predicate, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 5633766978029907089L; + + /** The value to compare to */ + private final T iValue; + + /** The equator to use for comparison */ + private final Equator equator; + + /** + * Factory to create the predicate. + * + * @param the type that the predicate queries + * @param object the object to compare to + * @return the predicate + */ + public static Predicate equalPredicate(final T object) { + if (object == null) { + return NullPredicate.nullPredicate(); + } + return new EqualPredicate(object); + } + + /** + * Factory to create the identity predicate. + * + * @param the type that the predicate queries + * @param object the object to compare to + * @param equator the equator to use for comparison + * @return the predicate + * @since 4.0 + */ + public static Predicate equalPredicate(final T object, final Equator equator) { + if (object == null) { + return NullPredicate.nullPredicate(); + } + return new EqualPredicate(object, equator); + } + + /** + * Constructor that performs no validation. + * Use equalPredicate if you want that. + * + * @param object the object to compare to + */ + public EqualPredicate(final T object) { + // do not use the DefaultEquator to keep backwards compatibility + // the DefaultEquator returns also true if the two object references are equal + this(object, null); + } + + /** + * Constructor that performs no validation. + * Use equalPredicate if you want that. + * + * @param object the object to compare to + * @param equator the equator to use for comparison + * @since 4.0 + */ + public EqualPredicate(final T object, final Equator equator) { + super(); + iValue = object; + this.equator = equator; + } + + /** + * Evaluates the predicate returning true if the input equals the stored value. + * + * @param object the input object + * @return true if input object equals stored value + */ + public boolean evaluate(final T object) { + if (equator != null) { + return equator.equate(iValue, object); + } else { + return iValue.equals(object); + } + } + + /** + * Gets the value. + * + * @return the value + * @since 3.1 + */ + public Object getValue() { + return iValue; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionClosure.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionClosure.java new file mode 100644 index 000000000..d1659ecbe --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionClosure.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Closure; +import org.apache.commons.collections4.FunctorException; + +/** + * Closure implementation that always throws an exception. + * + * @since 3.0 + * @version $Id: ExceptionClosure.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public final class ExceptionClosure implements Closure, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 7179106032121985545L; + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") // the static instance works for all types + public static final Closure INSTANCE = new ExceptionClosure(); + + /** + * Factory returning the singleton instance. + * + * @param the type that the closure acts on + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") // the static instance works for all types + public static Closure exceptionClosure() { + return (Closure) INSTANCE; + } + + /** + * Restricted constructor. + */ + private ExceptionClosure() { + super(); + } + + /** + * Always throw an exception. + * + * @param input the input object + * @throws FunctorException always + */ + public void execute(final E input) { + throw new FunctorException("ExceptionClosure invoked"); + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionFactory.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionFactory.java new file mode 100644 index 000000000..6656b8da2 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionFactory.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Factory; +import org.apache.commons.collections4.FunctorException; + +/** + * Factory implementation that always throws an exception. + * + * @since 3.0 + * @version $Id: ExceptionFactory.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public final class ExceptionFactory implements Factory, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 7179106032121985545L; + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") // the static instance works for all types + public static final Factory INSTANCE = new ExceptionFactory(); + + /** + * Factory returning the singleton instance. + * + * @param the type the factory creates + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") // the static instance works for all types + public static Factory exceptionFactory() { + return (Factory) INSTANCE; + } + + /** + * Restricted constructor. + */ + private ExceptionFactory() { + super(); + } + + /** + * Always throws an exception. + * + * @return never + * @throws FunctorException always + */ + public T create() { + throw new FunctorException("ExceptionFactory invoked"); + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionPredicate.java new file mode 100644 index 000000000..f35250501 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionPredicate.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.FunctorException; +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that always throws an exception. + * + * @since 3.0 + * @version $Id: ExceptionPredicate.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public final class ExceptionPredicate implements Predicate, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 7179106032121985545L; + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") // the static instance works for all types + public static final Predicate INSTANCE = new ExceptionPredicate(); + + /** + * Factory returning the singleton instance. + * + * @param the object type + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") // the static instance works for all types + public static Predicate exceptionPredicate() { + return (Predicate) INSTANCE; + } + + /** + * Restricted constructor. + */ + private ExceptionPredicate() { + super(); + } + + /** + * Evaluates the predicate always throwing an exception. + * + * @param object the input object + * @return never + * @throws FunctorException always + */ + public boolean evaluate(final T object) { + throw new FunctorException("ExceptionPredicate invoked"); + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionTransformer.java new file mode 100644 index 000000000..52ca32906 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ExceptionTransformer.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.FunctorException; +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that always throws an exception. + * + * @since 3.0 + * @version $Id: ExceptionTransformer.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public final class ExceptionTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 7179106032121985545L; + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") // the static instance works for all types + public static final Transformer INSTANCE = new ExceptionTransformer(); + + /** + * Factory returning the singleton instance. + * + * @param the input type + * @param the output type + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") // the static instance works for all types + public static Transformer exceptionTransformer() { + return (Transformer) INSTANCE; + } + + /** + * Restricted constructor. + */ + private ExceptionTransformer() { + super(); + } + + /** + * Transforms the input to result by cloning it. + * + * @param input the input object to transform + * @return never + * @throws FunctorException always + */ + public O transform(final I input) { + throw new FunctorException("ExceptionTransformer invoked"); + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/FactoryTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/FactoryTransformer.java new file mode 100644 index 000000000..c5929fdf8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/FactoryTransformer.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Factory; +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that calls a Factory and returns the result. + * + * @since 3.0 + * @version $Id: FactoryTransformer.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class FactoryTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -6817674502475353160L; + + /** The factory to wrap */ + private final Factory iFactory; + + /** + * Factory method that performs validation. + * + * @param the input type + * @param the output type + * @param factory the factory to call, not null + * @return the factory transformer + * @throws NullPointerException if the factory is null + */ + public static Transformer factoryTransformer(final Factory factory) { + if (factory == null) { + throw new NullPointerException("Factory must not be null"); + } + return new FactoryTransformer(factory); + } + + /** + * Constructor that performs no validation. + * Use factoryTransformer if you want that. + * + * @param factory the factory to call, not null + */ + public FactoryTransformer(final Factory factory) { + super(); + iFactory = factory; + } + + /** + * Transforms the input by ignoring the input and returning the result of + * calling the decorated factory. + * + * @param input the input object to transform + * @return the transformed result + */ + public O transform(final I input) { + return iFactory.create(); + } + + /** + * Gets the factory. + * + * @return the factory + * @since 3.1 + */ + public Factory getFactory() { + return iFactory; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/FalsePredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/FalsePredicate.java new file mode 100644 index 000000000..a09e83ad7 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/FalsePredicate.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that always returns false. + * + * @since 3.0 + * @version $Id: FalsePredicate.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public final class FalsePredicate implements Predicate, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 7533784454832764388L; + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") // the static instance works for all types + public static final Predicate INSTANCE = new FalsePredicate(); + + /** + * Get a typed instance. + * + * @param the type that the predicate queries + * @return the singleton instance + * @since 4.0 + */ + @SuppressWarnings("unchecked") // the static instance works for all types + public static Predicate falsePredicate() { + return (Predicate) INSTANCE; + } + + /** + * Restricted constructor. + */ + private FalsePredicate() { + super(); + } + + /** + * Evaluates the predicate returning false always. + * + * @param object the input object + * @return false always + */ + public boolean evaluate(final T object) { + return false; + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/ForClosure.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ForClosure.java new file mode 100644 index 000000000..0f8c45ede --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/ForClosure.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import org.apache.commons.collections4.Closure; + +/** + * Closure implementation that calls another closure n times, like a for loop. + *

          + * WARNING: from v4.1 onwards this class will not be serializable anymore + * in order to prevent potential remote code execution exploits. Please refer to + * COLLECTIONS-580 + * for more details. + * + * @since 3.0 + * @version $Id: ForClosure.java 1714262 2015-11-13 20:08:45Z tn $ + */ +public class ForClosure implements Closure { + + /** The number of times to loop */ + private final int iCount; + /** The closure to call */ + private final Closure iClosure; + + /** + * Factory method that performs validation. + *

          + * A null closure or zero count returns the NOPClosure. + * A count of one returns the specified closure. + * + * @param the type that the closure acts on + * @param count the number of times to execute the closure + * @param closure the closure to execute, not null + * @return the for closure + */ + @SuppressWarnings("unchecked") + public static Closure forClosure(final int count, final Closure closure) { + if (count <= 0 || closure == null) { + return NOPClosure.nopClosure(); + } + if (count == 1) { + return (Closure) closure; + } + return new ForClosure(count, closure); + } + + /** + * Constructor that performs no validation. + * Use forClosure if you want that. + * + * @param count the number of times to execute the closure + * @param closure the closure to execute, not null + */ + public ForClosure(final int count, final Closure closure) { + super(); + iCount = count; + iClosure = closure; + } + + /** + * Executes the closure count times. + * + * @param input the input object + */ + @Override + public void execute(final E input) { + for (int i = 0; i < iCount; i++) { + iClosure.execute(input); + } + } + + /** + * Gets the closure. + * + * @return the closure + * @since 3.1 + */ + public Closure getClosure() { + return iClosure; + } + + /** + * Gets the count. + * + * @return the count + * @since 3.1 + */ + public int getCount() { + return iCount; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/FunctorUtils.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/FunctorUtils.java new file mode 100644 index 000000000..41367443b --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/FunctorUtils.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.util.Collection; + +import org.apache.commons.collections4.Closure; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.Transformer; + +/** + * Internal utilities for functors. + * + * @since 3.0 + * @version $Id: FunctorUtils.java 1686855 2015-06-22 13:00:27Z tn $ + */ +class FunctorUtils { + + /** + * Restricted constructor. + */ + private FunctorUtils() { + super(); + } + + /** + * Clone the predicates to ensure that the internal reference can't be messed with. + * Due to the {@link Predicate#evaluate(T)} method, Predicate is + * able to be coerced to Predicate without casting issues. + * + * @param predicates the predicates to copy + * @return the cloned predicates + */ + @SuppressWarnings("unchecked") + static Predicate[] copy(final Predicate... predicates) { + if (predicates == null) { + return null; + } + return (Predicate[]) predicates.clone(); + } + + /** + * A very simple method that coerces Predicate to Predicate. + * Due to the {@link Predicate#evaluate(T)} method, Predicate is + * able to be coerced to Predicate without casting issues. + *

          This method exists + * simply as centralised documentation and atomic unchecked warning + * suppression. + * + * @param the type of object the returned predicate should "accept" + * @param predicate the predicate to coerce. + * @return the coerced predicate. + */ + @SuppressWarnings("unchecked") + static Predicate coerce(final Predicate predicate) { + return (Predicate) predicate; + } + + /** + * Validate the predicates to ensure that all is well. + * + * @param predicates the predicates to validate + */ + static void validate(final Predicate... predicates) { + if (predicates == null) { + throw new NullPointerException("The predicate array must not be null"); + } + for (int i = 0; i < predicates.length; i++) { + if (predicates[i] == null) { + throw new NullPointerException( + "The predicate array must not contain a null predicate, index " + i + " was null"); + } + } + } + + /** + * Validate the predicates to ensure that all is well. + * + * @param predicates the predicates to validate + * @return predicate array + */ + static Predicate[] validate(final Collection> predicates) { + if (predicates == null) { + throw new NullPointerException("The predicate collection must not be null"); + } + // convert to array like this to guarantee iterator() ordering + @SuppressWarnings("unchecked") // OK + final Predicate[] preds = new Predicate[predicates.size()]; + int i = 0; + for (final Predicate predicate : predicates) { + preds[i] = predicate; + if (preds[i] == null) { + throw new NullPointerException( + "The predicate collection must not contain a null predicate, index " + i + " was null"); + } + i++; + } + return preds; + } + + /** + * Clone the closures to ensure that the internal reference can't be messed with. + * + * @param closures the closures to copy + * @return the cloned closures + */ + @SuppressWarnings("unchecked") + static Closure[] copy(final Closure... closures) { + if (closures == null) { + return null; + } + return (Closure[]) closures.clone(); + } + + /** + * Validate the closures to ensure that all is well. + * + * @param closures the closures to validate + */ + static void validate(final Closure... closures) { + if (closures == null) { + throw new NullPointerException("The closure array must not be null"); + } + for (int i = 0; i < closures.length; i++) { + if (closures[i] == null) { + throw new NullPointerException( + "The closure array must not contain a null closure, index " + i + " was null"); + } + } + } + + /** + * A very simple method that coerces Closure to Closure. + *

          This method exists + * simply as centralised documentation and atomic unchecked warning + * suppression. + * + * @param the type of object the returned closure should "accept" + * @param closure the closure to coerce. + * @return the coerced closure. + */ + @SuppressWarnings("unchecked") + static Closure coerce(final Closure closure) { + return (Closure) closure; + } + + /** + * Copy method + * + * @param transformers the transformers to copy + * @return a clone of the transformers + */ + @SuppressWarnings("unchecked") + static Transformer[] copy(final Transformer... transformers) { + if (transformers == null) { + return null; + } + return (Transformer[]) transformers.clone(); + } + + /** + * Validate method + * + * @param transformers the transformers to validate + */ + static void validate(final Transformer... transformers) { + if (transformers == null) { + throw new NullPointerException("The transformer array must not be null"); + } + for (int i = 0; i < transformers.length; i++) { + if (transformers[i] == null) { + throw new NullPointerException( + "The transformer array must not contain a null transformer, index " + i + " was null"); + } + } + } + + /** + * A very simple method that coerces Transformer to Transformer. + *

          This method exists + * simply as centralised documentation and atomic unchecked warning + * suppression. + * + * @param the type of object the returned transformer should "accept" + * @param transformer the transformer to coerce. + * @return the coerced transformer. + */ + @SuppressWarnings("unchecked") + static Transformer coerce(final Transformer transformer) { + return (Transformer) transformer; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/IdentityPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/IdentityPredicate.java new file mode 100644 index 000000000..b3d66b608 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/IdentityPredicate.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if the input is the same object + * as the one stored in this predicate. + * + * @since 3.0 + * @version $Id: IdentityPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class IdentityPredicate implements Predicate, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -89901658494523293L; + + /** The value to compare to */ + private final T iValue; + + /** + * Factory to create the identity predicate. + * + * @param the type that the predicate queries + * @param object the object to compare to + * @return the predicate + */ + public static Predicate identityPredicate(final T object) { + if (object == null) { + return NullPredicate.nullPredicate(); + } + return new IdentityPredicate(object); + } + + /** + * Constructor that performs no validation. + * Use identityPredicate if you want that. + * + * @param object the object to compare to + */ + public IdentityPredicate(final T object) { + super(); + iValue = object; + } + + /** + * Evaluates the predicate returning true if the input object is identical to + * the stored object. + * + * @param object the input object + * @return true if input is the same object as the stored value + */ + public boolean evaluate(final T object) { + return iValue == object; + } + + /** + * Gets the value. + * + * @return the value + * @since 3.1 + */ + public T getValue() { + return iValue; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/IfClosure.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/IfClosure.java new file mode 100644 index 000000000..ce98a180e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/IfClosure.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Closure; +import org.apache.commons.collections4.Predicate; + +/** + * Closure implementation acts as an if statement calling one or other closure + * based on a predicate. + * + * @since 3.0 + * @version $Id: IfClosure.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class IfClosure implements Closure, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 3518477308466486130L; + + /** The test */ + private final Predicate iPredicate; + /** The closure to use if true */ + private final Closure iTrueClosure; + /** The closure to use if false */ + private final Closure iFalseClosure; + + /** + * Factory method that performs validation. + *

          + * This factory creates a closure that performs no action when + * the predicate is false. + * + * @param the type that the closure acts on + * @param predicate predicate to switch on + * @param trueClosure closure used if true + * @return the if closure + * @throws NullPointerException if either argument is null + * @since 3.2 + */ + public static Closure ifClosure(final Predicate predicate, final Closure trueClosure) { + return IfClosure.ifClosure(predicate, trueClosure, NOPClosure.nopClosure()); + } + + /** + * Factory method that performs validation. + * + * @param the type that the closure acts on + * @param predicate predicate to switch on + * @param trueClosure closure used if true + * @param falseClosure closure used if false + * @return the if closure + * @throws NullPointerException if any argument is null + */ + public static Closure ifClosure(final Predicate predicate, + final Closure trueClosure, + final Closure falseClosure) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + if (trueClosure == null || falseClosure == null) { + throw new NullPointerException("Closures must not be null"); + } + return new IfClosure(predicate, trueClosure, falseClosure); + } + + /** + * Constructor that performs no validation. + * Use ifClosure if you want that. + *

          + * This constructor creates a closure that performs no action when + * the predicate is false. + * + * @param predicate predicate to switch on, not null + * @param trueClosure closure used if true, not null + * @since 3.2 + */ + public IfClosure(final Predicate predicate, final Closure trueClosure) { + this(predicate, trueClosure, NOPClosure.nopClosure()); + } + + /** + * Constructor that performs no validation. + * Use ifClosure if you want that. + * + * @param predicate predicate to switch on, not null + * @param trueClosure closure used if true, not null + * @param falseClosure closure used if false, not null + */ + public IfClosure(final Predicate predicate, final Closure trueClosure, + final Closure falseClosure) { + super(); + iPredicate = predicate; + iTrueClosure = trueClosure; + iFalseClosure = falseClosure; + } + + /** + * Executes the true or false closure according to the result of the predicate. + * + * @param input the input object + */ + public void execute(final E input) { + if (iPredicate.evaluate(input)) { + iTrueClosure.execute(input); + } else { + iFalseClosure.execute(input); + } + } + + /** + * Gets the predicate. + * + * @return the predicate + * @since 3.1 + */ + public Predicate getPredicate() { + return iPredicate; + } + + /** + * Gets the closure called when true. + * + * @return the closure + * @since 3.1 + */ + public Closure getTrueClosure() { + return iTrueClosure; + } + + /** + * Gets the closure called when false. + * + * @return the closure + * @since 3.1 + */ + public Closure getFalseClosure() { + return iFalseClosure; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/IfTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/IfTransformer.java new file mode 100644 index 000000000..f5a12dd31 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/IfTransformer.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that will call one of two closures based on whether a predicate evaluates + * as true or false. + * + * @param The input type for the transformer + * @param The output type for the transformer + * + * @since 4.1 + * @version $Id: IfTransformer.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class IfTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 8069309411242014252L; + + /** The test */ + private final Predicate iPredicate; + /** The transformer to use if true */ + private final Transformer iTrueTransformer; + /** The transformer to use if false */ + private final Transformer iFalseTransformer; + + /** + * Factory method that performs validation. + * + * @param input type for the transformer + * @param output type for the transformer + * @param predicate predicate to switch on + * @param trueTransformer transformer used if true + * @param falseTransformer transformer used if false + * @return the if transformer + * @throws NullPointerException if either argument is null + */ + public static Transformer ifTransformer(final Predicate predicate, + final Transformer trueTransformer, + final Transformer falseTransformer) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + if (trueTransformer == null || falseTransformer == null) { + throw new NullPointerException("Transformers must not be null"); + } + + return new IfTransformer(predicate, trueTransformer, falseTransformer); + } + + /** + * Factory method that performs validation. + *

          + * This factory creates a transformer that just returns the input object when + * the predicate is false. + * + * @param input and output type for the transformer + * @param predicate predicate to switch on + * @param trueTransformer transformer used if true + * @return the if transformer + * @throws NullPointerException if either argument is null + */ + public static Transformer ifTransformer( + final Predicate predicate, + final Transformer trueTransformer) { + + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + if (trueTransformer == null) { + throw new NullPointerException("Transformer must not be null"); + } + + return new IfTransformer(predicate, trueTransformer, NOPTransformer.nopTransformer()); + } + + /** + * Constructor that performs no validation. + * Use the static factory method ifTransformer if you want that. + * + * @param predicate predicate to switch on, not null + * @param trueTransformer transformer used if true, not null + * @param falseTransformer transformer used if false, not null + */ + public IfTransformer(final Predicate predicate, + final Transformer trueTransformer, + final Transformer falseTransformer) { + + super(); + iPredicate = predicate; + iTrueTransformer = trueTransformer; + iFalseTransformer = falseTransformer; + } + + /** + * Transforms the input using the true or false transformer based to the result of the predicate. + * + * @param input the input object to transform + * @return the transformed result + */ + public O transform(final I input) { + if(iPredicate.evaluate(input)){ + return iTrueTransformer.transform(input); + } else { + return iFalseTransformer.transform(input); + } + } + + /** + * Gets the predicate. + * + * @return the predicate + */ + public Predicate getPredicate(){ + return iPredicate; + } + + /** + * Gets the transformer used when true. + * + * @return the transformer + */ + public Transformer getTrueTransformer() { + return iTrueTransformer; + } + + /** + * Gets the transformer used when false. + * + * @return the transformer + */ + public Transformer getFalseTransformer() { + return iFalseTransformer; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/InstanceofPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/InstanceofPredicate.java new file mode 100644 index 000000000..a5bf5ee48 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/InstanceofPredicate.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if the input is an instanceof + * the type stored in this predicate. + * + * @since 3.0 + * @version $Id: InstanceofPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class InstanceofPredicate implements Predicate, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -6682656911025165584L; + + /** The type to compare to */ + private final Class iType; + + /** + * Factory to create the identity predicate. + * + * @param type the type to check for, may not be null + * @return the predicate + * @throws NullPointerException if the class is null + */ + public static Predicate instanceOfPredicate(final Class type) { + if (type == null) { + throw new NullPointerException("The type to check instanceof must not be null"); + } + return new InstanceofPredicate(type); + } + + /** + * Constructor that performs no validation. + * Use instanceOfPredicate if you want that. + * + * @param type the type to check for + */ + public InstanceofPredicate(final Class type) { + super(); + iType = type; + } + + /** + * Evaluates the predicate returning true if the input object is of the correct type. + * + * @param object the input object + * @return true if input is of stored type + */ + public boolean evaluate(final Object object) { + return iType.isInstance(object); + } + + /** + * Gets the type to compare to. + * + * @return the type + * @since 3.1 + */ + public Class getType() { + return iType; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/InstantiateFactory.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/InstantiateFactory.java new file mode 100644 index 000000000..9375422c0 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/InstantiateFactory.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import org.apache.commons.collections4.Factory; +import org.apache.commons.collections4.FunctorException; + +/** + * Factory implementation that creates a new object instance by reflection. + *

          + * WARNING: from v4.1 onwards this class will not be serializable anymore + * in order to prevent potential remote code execution exploits. Please refer to + * COLLECTIONS-580 + * for more details. + * + * @since 3.0 + * @version $Id: InstantiateFactory.java 1714262 2015-11-13 20:08:45Z tn $ + */ +public class InstantiateFactory implements Factory { + + /** The class to create */ + private final Class iClassToInstantiate; + /** The constructor parameter types */ + private final Class[] iParamTypes; + /** The constructor arguments */ + private final Object[] iArgs; + /** The constructor */ + private transient Constructor iConstructor = null; + + /** + * Factory method that performs validation. + * + * @param the type the factory creates + * @param classToInstantiate the class to instantiate, not null + * @param paramTypes the constructor parameter types, cloned + * @param args the constructor arguments, cloned + * @return a new instantiate factory + * @throws NullPointerException if classToInstantiate is null + * @throws IllegalArgumentException if paramTypes does not match args + */ + public static Factory instantiateFactory(final Class classToInstantiate, + final Class[] paramTypes, + final Object[] args) { + if (classToInstantiate == null) { + throw new NullPointerException("Class to instantiate must not be null"); + } + if (paramTypes == null && args != null + || paramTypes != null && args == null + || paramTypes != null && args != null && paramTypes.length != args.length) { + throw new IllegalArgumentException("Parameter types must match the arguments"); + } + + if (paramTypes == null || paramTypes.length == 0) { + return new InstantiateFactory(classToInstantiate); + } + return new InstantiateFactory(classToInstantiate, paramTypes, args); + } + + /** + * Constructor that performs no validation. + * Use instantiateFactory if you want that. + * + * @param classToInstantiate the class to instantiate + */ + public InstantiateFactory(final Class classToInstantiate) { + super(); + iClassToInstantiate = classToInstantiate; + iParamTypes = null; + iArgs = null; + findConstructor(); + } + + /** + * Constructor that performs no validation. + * Use instantiateFactory if you want that. + * + * @param classToInstantiate the class to instantiate + * @param paramTypes the constructor parameter types, cloned + * @param args the constructor arguments, cloned + */ + public InstantiateFactory(final Class classToInstantiate, final Class[] paramTypes, final Object[] args) { + super(); + iClassToInstantiate = classToInstantiate; + iParamTypes = paramTypes.clone(); + iArgs = args.clone(); + findConstructor(); + } + + /** + * Find the Constructor for the class specified. + */ + private void findConstructor() { + try { + iConstructor = iClassToInstantiate.getConstructor(iParamTypes); + } catch (final NoSuchMethodException ex) { + throw new IllegalArgumentException("InstantiateFactory: The constructor must exist and be public "); + } + } + + /** + * Creates an object using the stored constructor. + * + * @return the new object + */ + @Override + public T create() { + // needed for post-serialization + if (iConstructor == null) { + findConstructor(); + } + + try { + return iConstructor.newInstance(iArgs); + } catch (final InstantiationException ex) { + throw new FunctorException("InstantiateFactory: InstantiationException", ex); + } catch (final IllegalAccessException ex) { + throw new FunctorException("InstantiateFactory: Constructor must be public", ex); + } catch (final InvocationTargetException ex) { + throw new FunctorException("InstantiateFactory: Constructor threw an exception", ex); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/InstantiateTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/InstantiateTransformer.java new file mode 100644 index 000000000..7fabe0fee --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/InstantiateTransformer.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import org.apache.commons.collections4.FunctorException; +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that creates a new object instance by reflection. + *

          + * WARNING: from v4.1 onwards this class will not be serializable anymore + * in order to prevent potential remote code execution exploits. Please refer to + * COLLECTIONS-580 + * for more details. + * + * @since 3.0 + * @version $Id: InstantiateTransformer.java 1714262 2015-11-13 20:08:45Z tn $ + */ +public class InstantiateTransformer implements Transformer, T> { + + /** Singleton instance that uses the no arg constructor */ + @SuppressWarnings("rawtypes") + private static final Transformer NO_ARG_INSTANCE = new InstantiateTransformer(); + + /** The constructor parameter types */ + private final Class[] iParamTypes; + /** The constructor arguments */ + private final Object[] iArgs; + + /** + * Get a typed no-arg instance. + * + * @param the type of the objects to be created + * @return Transformer, T> + */ + @SuppressWarnings("unchecked") + public static Transformer, T> instantiateTransformer() { + return NO_ARG_INSTANCE; + } + + /** + * Transformer method that performs validation. + * + * @param the type of the objects to be created + * @param paramTypes the constructor parameter types + * @param args the constructor arguments + * @return an instantiate transformer + * @throws IllegalArgumentException if paramTypes does not match args + */ + public static Transformer, T> instantiateTransformer(final Class[] paramTypes, + final Object[] args) { + if (((paramTypes == null) && (args != null)) + || ((paramTypes != null) && (args == null)) + || ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) { + throw new IllegalArgumentException("Parameter types must match the arguments"); + } + + if (paramTypes == null || paramTypes.length == 0) { + return new InstantiateTransformer(); + } + return new InstantiateTransformer(paramTypes, args); + } + + /** + * Constructor for no arg instance. + */ + private InstantiateTransformer() { + super(); + iParamTypes = null; + iArgs = null; + } + + /** + * Constructor that performs no validation. + * Use instantiateTransformer if you want that. + *

          + * Note: from 4.0, the input parameters will be cloned + * + * @param paramTypes the constructor parameter types + * @param args the constructor arguments + */ + public InstantiateTransformer(final Class[] paramTypes, final Object[] args) { + super(); + iParamTypes = paramTypes != null ? paramTypes.clone() : null; + iArgs = args != null ? args.clone() : null; + } + + /** + * Transforms the input Class object to a result by instantiation. + * + * @param input the input object to transform + * @return the transformed result + */ + @Override + public T transform(final Class input) { + try { + if (input == null) { + throw new FunctorException( + "InstantiateTransformer: Input object was not an instanceof Class, it was a null object"); + } + final Constructor con = input.getConstructor(iParamTypes); + return con.newInstance(iArgs); + } catch (final NoSuchMethodException ex) { + throw new FunctorException("InstantiateTransformer: The constructor must exist and be public "); + } catch (final InstantiationException ex) { + throw new FunctorException("InstantiateTransformer: InstantiationException", ex); + } catch (final IllegalAccessException ex) { + throw new FunctorException("InstantiateTransformer: Constructor must be public", ex); + } catch (final InvocationTargetException ex) { + throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/InvokerTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/InvokerTransformer.java new file mode 100644 index 000000000..32410009f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/InvokerTransformer.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.apache.commons.collections4.FunctorException; +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that creates a new object instance by reflection. + *

          + * WARNING: from v4.1 onwards this class will not be serializable anymore + * in order to prevent potential remote code execution exploits. Please refer to + * COLLECTIONS-580 + * for more details. + * + * @since 3.0 + * @version $Id: InvokerTransformer.java 1714262 2015-11-13 20:08:45Z tn $ + */ +public class InvokerTransformer implements Transformer { + + /** The method name to call */ + private final String iMethodName; + /** The array of reflection parameter types */ + private final Class[] iParamTypes; + /** The array of reflection arguments */ + private final Object[] iArgs; + + /** + * Gets an instance of this transformer calling a specific method with no arguments. + * + * @param the input type + * @param the output type + * @param methodName the method name to call + * @return an invoker transformer + * @throws NullPointerException if methodName is null + * @since 3.1 + */ + public static Transformer invokerTransformer(final String methodName) { + if (methodName == null) { + throw new NullPointerException("The method to invoke must not be null"); + } + return new InvokerTransformer(methodName); + } + + /** + * Gets an instance of this transformer calling a specific method with specific values. + * + * @param the input type + * @param the output type + * @param methodName the method name to call + * @param paramTypes the parameter types of the method + * @param args the arguments to pass to the method + * @return an invoker transformer + * @throws NullPointerException if methodName is null + * @throws IllegalArgumentException if paramTypes does not match args + */ + public static Transformer invokerTransformer(final String methodName, final Class[] paramTypes, + final Object[] args) { + if (methodName == null) { + throw new NullPointerException("The method to invoke must not be null"); + } + if (((paramTypes == null) && (args != null)) + || ((paramTypes != null) && (args == null)) + || ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) { + throw new IllegalArgumentException("The parameter types must match the arguments"); + } + if (paramTypes == null || paramTypes.length == 0) { + return new InvokerTransformer(methodName); + } + return new InvokerTransformer(methodName, paramTypes, args); + } + + /** + * Constructor for no arg instance. + * + * @param methodName the method to call + */ + private InvokerTransformer(final String methodName) { + super(); + iMethodName = methodName; + iParamTypes = null; + iArgs = null; + } + + /** + * Constructor that performs no validation. + * Use invokerTransformer if you want that. + *

          + * Note: from 4.0, the input parameters will be cloned + * + * @param methodName the method to call + * @param paramTypes the constructor parameter types + * @param args the constructor arguments + */ + public InvokerTransformer(final String methodName, final Class[] paramTypes, final Object[] args) { + super(); + iMethodName = methodName; + iParamTypes = paramTypes != null ? paramTypes.clone() : null; + iArgs = args != null ? args.clone() : null; + } + + /** + * Transforms the input to result by invoking a method on the input. + * + * @param input the input object to transform + * @return the transformed result, null if null input + */ + @Override + @SuppressWarnings("unchecked") + public O transform(final Object input) { + if (input == null) { + return null; + } + try { + final Class cls = input.getClass(); + final Method method = cls.getMethod(iMethodName, iParamTypes); + return (O) method.invoke(input, iArgs); + } catch (final NoSuchMethodException ex) { + throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + + input.getClass() + "' does not exist"); + } catch (final IllegalAccessException ex) { + throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + + input.getClass() + "' cannot be accessed"); + } catch (final InvocationTargetException ex) { + throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + + input.getClass() + "' threw an exception", ex); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/MapTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/MapTransformer.java new file mode 100644 index 000000000..7eed02c6e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/MapTransformer.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; +import java.util.Map; + +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that returns the value held in a specified map + * using the input parameter as a key. + * + * @since 3.0 + * @version $Id: MapTransformer.java 1476582 2013-04-27 14:13:54Z tn $ + */ +public final class MapTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 862391807045468939L; + + /** The map of data to lookup in */ + private final Map iMap; + + /** + * Factory to create the transformer. + *

          + * If the map is null, a transformer that always returns null is returned. + * + * @param the input type + * @param the output type + * @param map the map, not cloned + * @return the transformer + */ + public static Transformer mapTransformer(final Map map) { + if (map == null) { + return ConstantTransformer.nullTransformer(); + } + return new MapTransformer(map); + } + + /** + * Constructor that performs no validation. + * Use mapTransformer if you want that. + * + * @param map the map to use for lookup, not cloned + */ + private MapTransformer(final Map map) { + super(); + iMap = map; + } + + /** + * Transforms the input to result by looking it up in a Map. + * + * @param input the input object to transform + * @return the transformed result + */ + public O transform(final I input) { + return iMap.get(input); + } + + /** + * Gets the map to lookup in. + * + * @return the map + * @since 3.1 + */ + public Map getMap() { + return iMap; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/NOPClosure.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NOPClosure.java new file mode 100644 index 000000000..1a3091273 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NOPClosure.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Closure; + +/** + * Closure implementation that does nothing. + * + * @since 3.0 + * @version $Id: NOPClosure.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public final class NOPClosure implements Closure, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 3518477308466486130L; + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") + public static final Closure INSTANCE = new NOPClosure(); + + /** + * Factory returning the singleton instance. + * + * @param the type that the closure acts on + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public static Closure nopClosure() { + return (Closure) INSTANCE; + } + + /** + * Constructor. + */ + private NOPClosure() { + super(); + } + + /** + * Do nothing. + * + * @param input the input object + */ + public void execute(final E input) { + // do nothing + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/NOPTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NOPTransformer.java new file mode 100644 index 000000000..4c1d72690 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NOPTransformer.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that does nothing. + * + * @since 3.0 + * @version $Id: NOPTransformer.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public class NOPTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 2133891748318574490L; + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") + public static final Transformer INSTANCE = new NOPTransformer(); + + /** + * Factory returning the singleton instance. + * + * @param the input/output type + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public static Transformer nopTransformer() { + return (Transformer) INSTANCE; + } + + /** + * Constructor. + */ + private NOPTransformer() { + super(); + } + + /** + * Transforms the input to result by doing nothing. + * + * @param input the input object to transform + * @return the transformed result which is the input + */ + public T transform(final T input) { + return input; + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/NonePredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NonePredicate.java new file mode 100644 index 000000000..f29f42041 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NonePredicate.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.util.Collection; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if none of the + * predicates return true. + * If the array of predicates is empty, then this predicate returns true. + *

          + * NOTE: In versions prior to 3.2 an array size of zero or one + * threw an exception. + * + * @since 3.0 + * @version $Id: NonePredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class NonePredicate extends AbstractQuantifierPredicate { + + /** Serial version UID */ + private static final long serialVersionUID = 2007613066565892961L; + + /** + * Factory to create the predicate. + *

          + * If the array is size zero, the predicate always returns true. + * + * @param the type that the predicate queries + * @param predicates the predicates to check, cloned, not null + * @return the any predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + */ + public static Predicate nonePredicate(final Predicate... predicates) { + FunctorUtils.validate(predicates); + if (predicates.length == 0) { + return TruePredicate.truePredicate(); + } + return new NonePredicate(FunctorUtils.copy(predicates)); + } + + /** + * Factory to create the predicate. + *

          + * If the collection is size zero, the predicate always returns true. + * + * @param the type that the predicate queries + * @param predicates the predicates to check, cloned, not null + * @return the one predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + */ + public static Predicate nonePredicate(final Collection> predicates) { + final Predicate[] preds = FunctorUtils.validate(predicates); + if (preds.length == 0) { + return TruePredicate.truePredicate(); + } + return new NonePredicate(preds); + } + + /** + * Constructor that performs no validation. + * Use nonePredicate if you want that. + * + * @param predicates the predicates to check, not cloned, not null + */ + public NonePredicate(final Predicate... predicates) { + super(predicates); + } + + /** + * Evaluates the predicate returning false if any stored predicate returns false. + * + * @param object the input object + * @return true if none of decorated predicates return true + */ + public boolean evaluate(final T object) { + for (final Predicate iPredicate : iPredicates) { + if (iPredicate.evaluate(object)) { + return false; + } + } + return true; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/NotNullPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NotNullPredicate.java new file mode 100644 index 000000000..05a1b9d9e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NotNullPredicate.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if the input is not null. + * + * @since 3.0 + * @version $Id: NotNullPredicate.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public final class NotNullPredicate implements Predicate, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 7533784454832764388L; + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") + public static final Predicate INSTANCE = new NotNullPredicate(); + + /** + * Factory returning the singleton instance. + * + * @param the type that the predicate queries + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public static Predicate notNullPredicate() { + return (Predicate) INSTANCE; + } + + /** + * Restricted constructor. + */ + private NotNullPredicate() { + super(); + } + + /** + * Evaluates the predicate returning true if the object does not equal null. + * + * @param object the object to evaluate + * @return true if not null + */ + public boolean evaluate(final T object) { + return object != null; + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/NotPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NotPredicate.java new file mode 100644 index 000000000..6a3924f95 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NotPredicate.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns the opposite of the decorated predicate. + * + * @since 3.0 + * @version $Id: NotPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class NotPredicate implements PredicateDecorator, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -2654603322338049674L; + + /** The predicate to decorate */ + private final Predicate iPredicate; + + /** + * Factory to create the not predicate. + * + * @param the type that the predicate queries + * @param predicate the predicate to decorate, not null + * @return the predicate + * @throws NullPointerException if the predicate is null + */ + public static Predicate notPredicate(final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + return new NotPredicate(predicate); + } + + /** + * Constructor that performs no validation. + * Use notPredicate if you want that. + * + * @param predicate the predicate to call after the null check + */ + public NotPredicate(final Predicate predicate) { + super(); + iPredicate = predicate; + } + + /** + * Evaluates the predicate returning the opposite to the stored predicate. + * + * @param object the input object + * @return true if predicate returns false + */ + public boolean evaluate(final T object) { + return !iPredicate.evaluate(object); + } + + /** + * Gets the predicate being decorated. + * + * @return the predicate as the only element in an array + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public Predicate[] getPredicates() { + return new Predicate[] {iPredicate}; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsExceptionPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsExceptionPredicate.java new file mode 100644 index 000000000..77c34593d --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsExceptionPredicate.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.FunctorException; +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that throws an exception if the input is null. + * + * @since 3.0 + * @version $Id: NullIsExceptionPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class NullIsExceptionPredicate implements PredicateDecorator, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 3243449850504576071L; + + /** The predicate to decorate */ + private final Predicate iPredicate; + + /** + * Factory to create the null exception predicate. + * + * @param the type that the predicate queries + * @param predicate the predicate to decorate, not null + * @return the predicate + * @throws NullPointerException if the predicate is null + */ + public static Predicate nullIsExceptionPredicate(final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + return new NullIsExceptionPredicate(predicate); + } + + /** + * Constructor that performs no validation. + * Use nullIsExceptionPredicate if you want that. + * + * @param predicate the predicate to call after the null check + */ + public NullIsExceptionPredicate(final Predicate predicate) { + super(); + iPredicate = predicate; + } + + /** + * Evaluates the predicate returning the result of the decorated predicate + * once a null check is performed. + * + * @param object the input object + * @return true if decorated predicate returns true + * @throws FunctorException if input is null + */ + public boolean evaluate(final T object) { + if (object == null) { + throw new FunctorException("Input Object must not be null"); + } + return iPredicate.evaluate(object); + } + + /** + * Gets the predicate being decorated. + * + * @return the predicate as the only element in an array + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public Predicate[] getPredicates() { + return new Predicate[] { iPredicate }; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsFalsePredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsFalsePredicate.java new file mode 100644 index 000000000..69eefaa20 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsFalsePredicate.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns false if the input is null. + * + * @since 3.0 + * @version $Id: NullIsFalsePredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class NullIsFalsePredicate implements PredicateDecorator, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -2997501534564735525L; + + /** The predicate to decorate */ + private final Predicate iPredicate; + + /** + * Factory to create the null false predicate. + * + * @param the type that the predicate queries + * @param predicate the predicate to decorate, not null + * @return the predicate + * @throws NullPointerException if the predicate is null + */ + public static Predicate nullIsFalsePredicate(final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + return new NullIsFalsePredicate(predicate); + } + + /** + * Constructor that performs no validation. + * Use nullIsFalsePredicate if you want that. + * + * @param predicate the predicate to call after the null check + */ + public NullIsFalsePredicate(final Predicate predicate) { + super(); + iPredicate = predicate; + } + + /** + * Evaluates the predicate returning the result of the decorated predicate + * once a null check is performed. + * + * @param object the input object + * @return true if decorated predicate returns true, false if input is null + */ + public boolean evaluate(final T object) { + if (object == null) { + return false; + } + return iPredicate.evaluate(object); + } + + /** + * Gets the predicate being decorated. + * + * @return the predicate as the only element in an array + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public Predicate[] getPredicates() { + return new Predicate[] { iPredicate }; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsTruePredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsTruePredicate.java new file mode 100644 index 000000000..a5e06beaa --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullIsTruePredicate.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if the input is null. + * + * @since 3.0 + * @version $Id: NullIsTruePredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class NullIsTruePredicate implements PredicateDecorator, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -7625133768987126273L; + + /** The predicate to decorate */ + private final Predicate iPredicate; + + /** + * Factory to create the null true predicate. + * + * @param the type that the predicate queries + * @param predicate the predicate to decorate, not null + * @return the predicate + * @throws NullPointerException if the predicate is null + */ + public static Predicate nullIsTruePredicate(final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + return new NullIsTruePredicate(predicate); + } + + /** + * Constructor that performs no validation. + * Use nullIsTruePredicate if you want that. + * + * @param predicate the predicate to call after the null check + */ + public NullIsTruePredicate(final Predicate predicate) { + super(); + iPredicate = predicate; + } + + /** + * Evaluates the predicate returning the result of the decorated predicate + * once a null check is performed. + * + * @param object the input object + * @return true if decorated predicate returns true or input is null + */ + public boolean evaluate(final T object) { + if (object == null) { + return true; + } + return iPredicate.evaluate(object); + } + + /** + * Gets the predicate being decorated. + * + * @return the predicate as the only element in an array + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public Predicate[] getPredicates() { + return new Predicate[] { iPredicate }; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullPredicate.java new file mode 100644 index 000000000..5924bb6c9 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/NullPredicate.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if the input is null. + * + * @since 3.0 + * @version $Id: NullPredicate.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public final class NullPredicate implements Predicate, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 7533784454832764388L; + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") + public static final Predicate INSTANCE = new NullPredicate(); + + /** + * Factory returning the singleton instance. + * + * @param the type that the predicate queries + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public static Predicate nullPredicate() { + return (Predicate) INSTANCE; + } + + /** + * Restricted constructor. + */ + private NullPredicate() { + super(); + } + + /** + * Evaluates the predicate returning true if the input is null. + * + * @param object the input object + * @return true if input is null + */ + public boolean evaluate(final T object) { + return object == null; + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/OnePredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/OnePredicate.java new file mode 100644 index 000000000..c4661cec9 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/OnePredicate.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.util.Collection; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if only one of the + * predicates return true. + * If the array of predicates is empty, then this predicate returns false. + *

          + * NOTE: In versions prior to 3.2 an array size of zero or one + * threw an exception. + * + * @since 3.0 + * @version $Id: OnePredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class OnePredicate extends AbstractQuantifierPredicate { + + /** Serial version UID */ + private static final long serialVersionUID = -8125389089924745785L; + + /** + * Factory to create the predicate. + *

          + * If the array is size zero, the predicate always returns false. + * If the array is size one, then that predicate is returned. + * + * @param the type that the predicate queries + * @param predicates the predicates to check, cloned, not null + * @return the any predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + */ + @SuppressWarnings("unchecked") + public static Predicate onePredicate(final Predicate... predicates) { + FunctorUtils.validate(predicates); + if (predicates.length == 0) { + return FalsePredicate.falsePredicate(); + } + if (predicates.length == 1) { + return (Predicate) predicates[0]; + } + return new OnePredicate(FunctorUtils.copy(predicates)); + } + + /** + * Factory to create the predicate. + * + * @param the type that the predicate queries + * @param predicates the predicates to check, cloned, not null + * @return the one predicate + * @throws NullPointerException if the predicates array is null + * @throws NullPointerException if any predicate in the array is null + */ + public static Predicate onePredicate(final Collection> predicates) { + final Predicate[] preds = FunctorUtils.validate(predicates); + return new OnePredicate(preds); + } + + /** + * Constructor that performs no validation. + * Use onePredicate if you want that. + * + * @param predicates the predicates to check, not cloned, not null + */ + public OnePredicate(final Predicate... predicates) { + super(predicates); + } + + /** + * Evaluates the predicate returning true if only one decorated predicate + * returns true. + * + * @param object the input object + * @return true if only one decorated predicate returns true + */ + public boolean evaluate(final T object) { + boolean match = false; + for (final Predicate iPredicate : iPredicates) { + if (iPredicate.evaluate(object)) { + if (match) { + return false; + } + match = true; + } + } + return match; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/OrPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/OrPredicate.java new file mode 100644 index 000000000..3a89a1fd4 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/OrPredicate.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true if either of the predicates return true. + * + * @since 3.0 + * @version $Id: OrPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class OrPredicate implements PredicateDecorator, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -8791518325735182855L; + + /** The array of predicates to call */ + private final Predicate iPredicate1; + /** The array of predicates to call */ + private final Predicate iPredicate2; + + /** + * Factory to create the predicate. + * + * @param the type that the predicate queries + * @param predicate1 the first predicate to check, not null + * @param predicate2 the second predicate to check, not null + * @return the and predicate + * @throws NullPointerException if either predicate is null + */ + public static Predicate orPredicate(final Predicate predicate1, + final Predicate predicate2) { + if (predicate1 == null || predicate2 == null) { + throw new NullPointerException("Predicate must not be null"); + } + return new OrPredicate(predicate1, predicate2); + } + + /** + * Constructor that performs no validation. + * Use orPredicate if you want that. + * + * @param predicate1 the first predicate to check, not null + * @param predicate2 the second predicate to check, not null + */ + public OrPredicate(final Predicate predicate1, final Predicate predicate2) { + super(); + iPredicate1 = predicate1; + iPredicate2 = predicate2; + } + + /** + * Evaluates the predicate returning true if either predicate returns true. + * + * @param object the input object + * @return true if either decorated predicate returns true + */ + public boolean evaluate(final T object) { + return iPredicate1.evaluate(object) || iPredicate2.evaluate(object); + } + + /** + * Gets the two predicates being decorated as an array. + * + * @return the predicates + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public Predicate[] getPredicates() { + return new Predicate[] {iPredicate1, iPredicate2}; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/PredicateDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/PredicateDecorator.java new file mode 100644 index 000000000..19917acdb --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/PredicateDecorator.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import org.apache.commons.collections4.Predicate; + +/** + * Defines a predicate that decorates one or more other predicates. + *

          + * This interface enables tools to access the decorated predicates. + * + * @since 3.1 + * @version $Id: PredicateDecorator.java 1477798 2013-04-30 19:49:02Z tn $ + */ +public interface PredicateDecorator extends Predicate { + + /** + * Gets the predicates being decorated as an array. + *

          + * The array may be the internal data structure of the predicate and thus + * should not be altered. + * + * @return the predicates being decorated + */ + Predicate[] getPredicates(); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/PredicateTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/PredicateTransformer.java new file mode 100644 index 000000000..058816b7a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/PredicateTransformer.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that calls a Predicate using the input object + * and then returns the result. + * + * @since 3.0 + * @version $Id: PredicateTransformer.java 1477798 2013-04-30 19:49:02Z tn $ + */ +public class PredicateTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 5278818408044349346L; + + /** The closure to wrap */ + private final Predicate iPredicate; + + /** + * Factory method that performs validation. + * + * @param the input type + * @param predicate the predicate to call, not null + * @return the predicate transformer + * @throws IllegalArgumentException if the predicate is null + */ + public static Transformer predicateTransformer(final Predicate predicate) { + if (predicate == null) { + throw new IllegalArgumentException("Predicate must not be null"); + } + return new PredicateTransformer(predicate); + } + + /** + * Constructor that performs no validation. + * Use predicateTransformer if you want that. + * + * @param predicate the predicate to call, not null + */ + public PredicateTransformer(final Predicate predicate) { + super(); + iPredicate = predicate; + } + + /** + * Transforms the input to result by calling a predicate. + * + * @param input the input object to transform + * @return the transformed result + */ + public Boolean transform(final T input) { + return Boolean.valueOf(iPredicate.evaluate(input)); + } + + /** + * Gets the predicate. + * + * @return the predicate + * @since 3.1 + */ + public Predicate getPredicate() { + return iPredicate; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/PrototypeFactory.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/PrototypeFactory.java new file mode 100644 index 000000000..b5d167621 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/PrototypeFactory.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.apache.commons.collections4.Factory; +import org.apache.commons.collections4.FunctorException; + +/** + * Factory implementation that creates a new instance each time based on a prototype. + *

          + * WARNING: from v4.1 onwards {@link Factory} instances returned by + * {@link #prototypeFactory(Object)} will not be serializable anymore in order + * to prevent potential remote code execution exploits. Please refer to + * COLLECTIONS-580 + * for more details. + * + * @since 3.0 + * @version $Id: PrototypeFactory.java 1714262 2015-11-13 20:08:45Z tn $ + */ +public class PrototypeFactory { + + /** + * Factory method that performs validation. + *

          + * Creates a Factory that will return a clone of the same prototype object + * each time the factory is used. The prototype will be cloned using one of these + * techniques (in order): + *

            + *
          • public clone method + *
          • public copy constructor + *
          • serialization clone + *
              + * + * @param the type the factory creates + * @param prototype the object to clone each time in the factory + * @return the prototype factory, or a {@link ConstantFactory#NULL_INSTANCE} if + * the {@code prototype} is {@code null} + * @throws IllegalArgumentException if the prototype cannot be cloned + */ + @SuppressWarnings("unchecked") + public static Factory prototypeFactory(final T prototype) { + if (prototype == null) { + return ConstantFactory.constantFactory(null); + } + try { + final Method method = prototype.getClass().getMethod("clone", (Class[]) null); + return new PrototypeCloneFactory(prototype, method); + + } catch (final NoSuchMethodException ex) { + try { + prototype.getClass().getConstructor(new Class[] { prototype.getClass() }); + return new InstantiateFactory( + (Class) prototype.getClass(), + new Class[] { prototype.getClass() }, + new Object[] { prototype }); + } catch (final NoSuchMethodException ex2) { + if (prototype instanceof Serializable) { + return (Factory) new PrototypeSerializationFactory((Serializable) prototype); + } + } + } + throw new IllegalArgumentException("The prototype must be cloneable via a public clone method"); + } + + /** + * Restricted constructor. + */ + private PrototypeFactory() { + super(); + } + + // PrototypeCloneFactory + //----------------------------------------------------------------------- + /** + * PrototypeCloneFactory creates objects by copying a prototype using the clone method. + */ + static class PrototypeCloneFactory implements Factory { + + /** The object to clone each time */ + private final T iPrototype; + /** The method used to clone */ + private transient Method iCloneMethod; + + /** + * Constructor to store prototype. + */ + private PrototypeCloneFactory(final T prototype, final Method method) { + super(); + iPrototype = prototype; + iCloneMethod = method; + } + + /** + * Find the Clone method for the class specified. + */ + private void findCloneMethod() { + try { + iCloneMethod = iPrototype.getClass().getMethod("clone", (Class[]) null); + } catch (final NoSuchMethodException ex) { + throw new IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public "); + } + } + + /** + * Creates an object by calling the clone method. + * + * @return the new object + */ + @Override + @SuppressWarnings("unchecked") + public T create() { + // needed for post-serialization + if (iCloneMethod == null) { + findCloneMethod(); + } + + try { + return (T) iCloneMethod.invoke(iPrototype, (Object[]) null); + } catch (final IllegalAccessException ex) { + throw new FunctorException("PrototypeCloneFactory: Clone method must be public", ex); + } catch (final InvocationTargetException ex) { + throw new FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex); + } + } + } + + // PrototypeSerializationFactory + //----------------------------------------------------------------------- + /** + * PrototypeSerializationFactory creates objects by cloning a prototype using serialization. + */ + static class PrototypeSerializationFactory implements Factory { + + /** The object to clone via serialization each time */ + private final T iPrototype; + + /** + * Constructor to store prototype + */ + private PrototypeSerializationFactory(final T prototype) { + super(); + iPrototype = prototype; + } + + /** + * Creates an object using serialization. + * + * @return the new object + */ + @Override + @SuppressWarnings("unchecked") + public T create() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(512); + ByteArrayInputStream bais = null; + try { + final ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(iPrototype); + + bais = new ByteArrayInputStream(baos.toByteArray()); + final ObjectInputStream in = new ObjectInputStream(bais); + return (T) in.readObject(); + + } catch (final ClassNotFoundException ex) { + throw new FunctorException(ex); + } catch (final IOException ex) { + throw new FunctorException(ex); + } finally { + try { + if (bais != null) { + bais.close(); + } + } catch (final IOException ex) { + // ignore + } + try { + baos.close(); + } catch (final IOException ex) { + // ignore + } + } + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/StringValueTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/StringValueTransformer.java new file mode 100644 index 000000000..f26a74c10 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/StringValueTransformer.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation that returns the result of calling + * String.valueOf on the input object. + * + * @since 3.0 + * @version $Id: StringValueTransformer.java 1543964 2013-11-20 21:53:39Z tn $ + */ +public final class StringValueTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 7511110693171758606L; + + /** Singleton predicate instance */ + private static final Transformer INSTANCE = new StringValueTransformer(); + + /** + * Factory returning the singleton instance. + * + * @param the input type + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public static Transformer stringValueTransformer() { + return (Transformer) INSTANCE; + } + + /** + * Restricted constructor. + */ + private StringValueTransformer() { + super(); + } + + /** + * Transforms the input to result by calling String.valueOf. + * + * @param input the input object to transform + * @return the transformed result + */ + public String transform(final T input) { + return String.valueOf(input); + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/SwitchClosure.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/SwitchClosure.java new file mode 100644 index 000000000..b484dd7b8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/SwitchClosure.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; +import java.util.Map; + +import org.apache.commons.collections4.Closure; +import org.apache.commons.collections4.Predicate; + +/** + * Closure implementation calls the closure whose predicate returns true, + * like a switch statement. + * + * @since 3.0 + * @version $Id: SwitchClosure.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class SwitchClosure implements Closure, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 3518477308466486130L; + + /** The tests to consider */ + private final Predicate[] iPredicates; + /** The matching closures to call */ + private final Closure[] iClosures; + /** The default closure to call if no tests match */ + private final Closure iDefault; + + /** + * Factory method that performs validation and copies the parameter arrays. + * + * @param the type that the closure acts on + * @param predicates array of predicates, cloned, no nulls + * @param closures matching array of closures, cloned, no nulls + * @param defaultClosure the closure to use if no match, null means nop + * @return the chained closure + * @throws NullPointerException if array is null + * @throws NullPointerException if any element in the array is null + * @throws IllegalArgumentException if the array lengths of predicates and closures do not match + */ + @SuppressWarnings("unchecked") + public static Closure switchClosure(final Predicate[] predicates, + final Closure[] closures, + final Closure defaultClosure) { + FunctorUtils.validate(predicates); + FunctorUtils.validate(closures); + if (predicates.length != closures.length) { + throw new IllegalArgumentException("The predicate and closure arrays must be the same size"); + } + if (predicates.length == 0) { + return (Closure) (defaultClosure == null ? NOPClosure.nopClosure(): defaultClosure); + } + return new SwitchClosure(predicates, closures, defaultClosure); + } + + /** + * Create a new Closure that calls one of the closures depending + * on the predicates. + *

              + * The Map consists of Predicate keys and Closure values. A closure + * is called if its matching predicate returns true. Each predicate is evaluated + * until one returns true. If no predicates evaluate to true, the default + * closure is called. The default closure is set in the map with a + * null key. The ordering is that of the iterator() method on the entryset + * collection of the map. + * + * @param the type that the closure acts on + * @param predicatesAndClosures a map of predicates to closures + * @return the switch closure + * @throws NullPointerException if the map is null + * @throws NullPointerException if any closure in the map is null + * @throws ClassCastException if the map elements are of the wrong type + */ + @SuppressWarnings("unchecked") + public static Closure switchClosure(final Map, Closure> predicatesAndClosures) { + if (predicatesAndClosures == null) { + throw new NullPointerException("The predicate and closure map must not be null"); + } + // convert to array like this to guarantee iterator() ordering + final Closure defaultClosure = predicatesAndClosures.remove(null); + final int size = predicatesAndClosures.size(); + if (size == 0) { + return (Closure) (defaultClosure == null ? NOPClosure.nopClosure() : defaultClosure); + } + final Closure[] closures = new Closure[size]; + final Predicate[] preds = new Predicate[size]; + int i = 0; + for (final Map.Entry, Closure> entry : predicatesAndClosures.entrySet()) { + preds[i] = entry.getKey(); + closures[i] = entry.getValue(); + i++; + } + return new SwitchClosure(false, preds, closures, defaultClosure); + } + + /** + * Hidden constructor for the use by the static factory methods. + * + * @param clone if {@code true} the input arguments will be cloned + * @param predicates array of predicates, no nulls + * @param closures matching array of closures, no nulls + * @param defaultClosure the closure to use if no match, null means nop + */ + @SuppressWarnings("unchecked") + private SwitchClosure(final boolean clone, final Predicate[] predicates, + final Closure[] closures, final Closure defaultClosure) { + super(); + iPredicates = clone ? FunctorUtils.copy(predicates) : predicates; + iClosures = clone ? FunctorUtils.copy(closures) : closures; + iDefault = (Closure) (defaultClosure == null ? NOPClosure.nopClosure() : defaultClosure); + } + + /** + * Constructor that performs no validation. + * Use switchClosure if you want that. + * + * @param predicates array of predicates, cloned, no nulls + * @param closures matching array of closures, cloned, no nulls + * @param defaultClosure the closure to use if no match, null means nop + */ + public SwitchClosure(final Predicate[] predicates, final Closure[] closures, + final Closure defaultClosure) { + this(true, predicates, closures, defaultClosure); + } + + /** + * Executes the closure whose matching predicate returns true + * + * @param input the input object + */ + public void execute(final E input) { + for (int i = 0; i < iPredicates.length; i++) { + if (iPredicates[i].evaluate(input) == true) { + iClosures[i].execute(input); + return; + } + } + iDefault.execute(input); + } + + /** + * Gets the predicates. + * + * @return a copy of the predicates + * @since 3.1 + */ + public Predicate[] getPredicates() { + return FunctorUtils.copy(iPredicates); + } + + /** + * Gets the closures. + * + * @return a copy of the closures + * @since 3.1 + */ + public Closure[] getClosures() { + return FunctorUtils.copy(iClosures); + } + + /** + * Gets the default closure. + * + * @return the default closure + * @since 3.1 + */ + public Closure getDefaultClosure() { + return iDefault; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/SwitchTransformer.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/SwitchTransformer.java new file mode 100644 index 000000000..e83f10be2 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/SwitchTransformer.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; +import java.util.Map; + +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.Transformer; + +/** + * Transformer implementation calls the transformer whose predicate returns true, + * like a switch statement. + * + * @since 3.0 + * @version $Id: SwitchTransformer.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class SwitchTransformer implements Transformer, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -6404460890903469332L; + + /** The tests to consider */ + private final Predicate[] iPredicates; + /** The matching transformers to call */ + private final Transformer[] iTransformers; + /** The default transformer to call if no tests match */ + private final Transformer iDefault; + + /** + * Factory method that performs validation and copies the parameter arrays. + * + * @param the input type + * @param the output type + * @param predicates array of predicates, cloned, no nulls + * @param transformers matching array of transformers, cloned, no nulls + * @param defaultTransformer the transformer to use if no match, null means return null + * @return the chained transformer + * @throws NullPointerException if array is null + * @throws NullPointerException if any element in the array is null + */ + @SuppressWarnings("unchecked") + public static Transformer switchTransformer(final Predicate[] predicates, + final Transformer[] transformers, + final Transformer defaultTransformer) { + FunctorUtils.validate(predicates); + FunctorUtils.validate(transformers); + if (predicates.length != transformers.length) { + throw new IllegalArgumentException("The predicate and transformer arrays must be the same size"); + } + if (predicates.length == 0) { + return (Transformer) (defaultTransformer == null ? ConstantTransformer.nullTransformer() : + defaultTransformer); + } + return new SwitchTransformer(predicates, transformers, defaultTransformer); + } + + /** + * Create a new Transformer that calls one of the transformers depending + * on the predicates. + *

              + * The Map consists of Predicate keys and Transformer values. A transformer + * is called if its matching predicate returns true. Each predicate is evaluated + * until one returns true. If no predicates evaluate to true, the default + * transformer is called. The default transformer is set in the map with a + * null key. The ordering is that of the iterator() method on the entryset + * collection of the map. + * + * @param the input type + * @param the output type + * @param map a map of predicates to transformers + * @return the switch transformer + * @throws NullPointerException if the map is null + * @throws NullPointerException if any transformer in the map is null + * @throws ClassCastException if the map elements are of the wrong type + */ + @SuppressWarnings("unchecked") + public static Transformer switchTransformer( + final Map, ? extends Transformer> map) { + + if (map == null) { + throw new NullPointerException("The predicate and transformer map must not be null"); + } + if (map.size() == 0) { + return ConstantTransformer.nullTransformer(); + } + // convert to array like this to guarantee iterator() ordering + final Transformer defaultTransformer = map.remove(null); + final int size = map.size(); + if (size == 0) { + return (Transformer) (defaultTransformer == null ? ConstantTransformer.nullTransformer() : + defaultTransformer); + } + final Transformer[] transformers = new Transformer[size]; + final Predicate[] preds = new Predicate[size]; + int i = 0; + for (final Map.Entry, + ? extends Transformer> entry : map.entrySet()) { + preds[i] = entry.getKey(); + transformers[i] = entry.getValue(); + i++; + } + return new SwitchTransformer(false, preds, transformers, defaultTransformer); + } + + /** + * Hidden constructor for the use by the static factory methods. + * + * @param clone if {@code true} the input arguments will be cloned + * @param predicates array of predicates, no nulls + * @param transformers matching array of transformers, no nulls + * @param defaultTransformer the transformer to use if no match, null means return null + */ + @SuppressWarnings("unchecked") + private SwitchTransformer(final boolean clone, final Predicate[] predicates, + final Transformer[] transformers, + final Transformer defaultTransformer) { + super(); + iPredicates = clone ? FunctorUtils.copy(predicates) : predicates; + iTransformers = clone ? FunctorUtils.copy(transformers) : transformers; + iDefault = (Transformer) (defaultTransformer == null ? + ConstantTransformer.nullTransformer() : defaultTransformer); + } + + /** + * Constructor that performs no validation. + * Use switchTransformer if you want that. + * + * @param predicates array of predicates, cloned, no nulls + * @param transformers matching array of transformers, cloned, no nulls + * @param defaultTransformer the transformer to use if no match, null means return null + */ + public SwitchTransformer(final Predicate[] predicates, + final Transformer[] transformers, + final Transformer defaultTransformer) { + this(true, predicates, transformers, defaultTransformer); + } + + /** + * Transforms the input to result by calling the transformer whose matching + * predicate returns true. + * + * @param input the input object to transform + * @return the transformed result + */ + public O transform(final I input) { + for (int i = 0; i < iPredicates.length; i++) { + if (iPredicates[i].evaluate(input) == true) { + return iTransformers[i].transform(input); + } + } + return iDefault.transform(input); + } + + /** + * Gets the predicates. + * + * @return a copy of the predicates + * @since 3.1 + */ + public Predicate[] getPredicates() { + return FunctorUtils.copy(iPredicates); + } + + /** + * Gets the transformers. + * + * @return a copy of the transformers + * @since 3.1 + */ + public Transformer[] getTransformers() { + return FunctorUtils.copy(iTransformers); + } + + /** + * Gets the default transformer. + * + * @return the default transformer + * @since 3.1 + */ + public Transformer getDefaultTransformer() { + return iDefault; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformedPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformedPredicate.java new file mode 100644 index 000000000..a19fe8ba1 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformedPredicate.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.Transformer; + +/** + * Predicate implementation that transforms the given object before invoking + * another Predicate. + * + * @since 3.1 + * @version $Id: TransformedPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class TransformedPredicate implements PredicateDecorator, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -5596090919668315834L; + + /** The transformer to call */ + private final Transformer iTransformer; + + /** The predicate to call */ + private final Predicate iPredicate; + + /** + * Factory to create the predicate. + * + * @param the type that the predicate queries + * @param transformer the transformer to call + * @param predicate the predicate to call with the result of the transform + * @return the predicate + * @throws NullPointerException if the transformer or the predicate is null + */ + public static Predicate transformedPredicate(final Transformer transformer, + final Predicate predicate) { + if (transformer == null) { + throw new NullPointerException("The transformer to call must not be null"); + } + if (predicate == null) { + throw new NullPointerException("The predicate to call must not be null"); + } + return new TransformedPredicate(transformer, predicate); + } + + /** + * Constructor that performs no validation. + * Use transformedPredicate if you want that. + * + * @param transformer the transformer to use + * @param predicate the predicate to decorate + */ + public TransformedPredicate(final Transformer transformer, + final Predicate predicate) { + iTransformer = transformer; + iPredicate = predicate; + } + + /** + * Evaluates the predicate returning the result of the decorated predicate + * once the input has been transformed + * + * @param object the input object which will be transformed + * @return true if decorated predicate returns true + */ + public boolean evaluate(final T object) { + final T result = iTransformer.transform(object); + return iPredicate.evaluate(result); + } + + /** + * Gets the predicate being decorated. + * + * @return the predicate as the only element in an array + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public Predicate[] getPredicates() { + return new Predicate[] {iPredicate}; + } + + /** + * Gets the transformer in use. + * + * @return the transformer + */ + public Transformer getTransformer() { + return iTransformer; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformerClosure.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformerClosure.java new file mode 100644 index 000000000..77def81f3 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformerClosure.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Closure; +import org.apache.commons.collections4.Transformer; + +/** + * Closure implementation that calls a Transformer using the input object + * and ignore the result. + * + * @since 3.0 + * @version $Id: TransformerClosure.java 1477798 2013-04-30 19:49:02Z tn $ + */ +public class TransformerClosure implements Closure, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -5194992589193388969L; + + /** The transformer to wrap */ + private final Transformer iTransformer; + + /** + * Factory method that performs validation. + *

              + * A null transformer will return the NOPClosure. + * + * @param the type that the closure acts on + * @param transformer the transformer to call, null means nop + * @return the transformer closure + */ + public static Closure transformerClosure(final Transformer transformer) { + if (transformer == null) { + return NOPClosure.nopClosure(); + } + return new TransformerClosure(transformer); + } + + /** + * Constructor that performs no validation. + * Use transformerClosure if you want that. + * + * @param transformer the transformer to call, not null + */ + public TransformerClosure(final Transformer transformer) { + super(); + iTransformer = transformer; + } + + /** + * Executes the closure by calling the decorated transformer. + * + * @param input the input object + */ + public void execute(final E input) { + iTransformer.transform(input); + } + + /** + * Gets the transformer. + * + * @return the transformer + * @since 3.1 + */ + public Transformer getTransformer() { + return iTransformer; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformerPredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformerPredicate.java new file mode 100644 index 000000000..5dc95bb16 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/TransformerPredicate.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.FunctorException; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.Transformer; + +/** + * Predicate implementation that returns the result of a transformer. + * + * @since 3.0 + * @version $Id: TransformerPredicate.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class TransformerPredicate implements Predicate, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -2407966402920578741L; + + /** The transformer to call */ + private final Transformer iTransformer; + + /** + * Factory to create the predicate. + * + * @param the type that the predicate queries + * @param transformer the transformer to decorate + * @return the predicate + * @throws NullPointerException if the transformer is null + */ + public static Predicate transformerPredicate(final Transformer transformer) { + if (transformer == null) { + throw new NullPointerException("The transformer to call must not be null"); + } + return new TransformerPredicate(transformer); + } + + /** + * Constructor that performs no validation. + * Use transformerPredicate if you want that. + * + * @param transformer the transformer to decorate + */ + public TransformerPredicate(final Transformer transformer) { + super(); + iTransformer = transformer; + } + + /** + * Evaluates the predicate returning the result of the decorated transformer. + * + * @param object the input object + * @return true if decorated transformer returns Boolean.TRUE + * @throws FunctorException if the transformer returns an invalid type + */ + public boolean evaluate(final T object) { + final Boolean result = iTransformer.transform(object); + if (result == null) { + throw new FunctorException( + "Transformer must return an instanceof Boolean, it was a null object"); + } + return result.booleanValue(); + } + + /** + * Gets the transformer. + * + * @return the transformer + * @since 3.1 + */ + public Transformer getTransformer() { + return iTransformer; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/TruePredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/TruePredicate.java new file mode 100644 index 000000000..757f139fa --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/TruePredicate.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that always returns true. + * + * @since 3.0 + * @version $Id: TruePredicate.java 1543950 2013-11-20 21:13:35Z tn $ + */ +public final class TruePredicate implements Predicate, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = 3374767158756189740L; + + /** Singleton predicate instance */ + @SuppressWarnings("rawtypes") + public static final Predicate INSTANCE = new TruePredicate(); + + /** + * Factory returning the singleton instance. + * + * @param the type that the predicate queries + * @return the singleton instance + * @since 3.1 + */ + @SuppressWarnings("unchecked") + public static Predicate truePredicate() { + return (Predicate) INSTANCE; + } + + /** + * Restricted constructor. + */ + private TruePredicate() { + super(); + } + + /** + * Evaluates the predicate returning true always. + * + * @param object the input object + * @return true always + */ + public boolean evaluate(final T object) { + return true; + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/UniquePredicate.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/UniquePredicate.java new file mode 100644 index 000000000..64be48cc8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/UniquePredicate.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.collections4.Predicate; + +/** + * Predicate implementation that returns true the first time an object is + * passed into the predicate. + * + * @since 3.0 + * @version $Id: UniquePredicate.java 1476582 2013-04-27 14:13:54Z tn $ + */ +public final class UniquePredicate implements Predicate, Serializable { + + /** Serial version UID */ + private static final long serialVersionUID = -3319417438027438040L; + + /** The set of previously seen objects */ + private final Set iSet = new HashSet(); + + /** + * Factory to create the predicate. + * + * @param the type that the predicate queries + * @return the predicate + * @throws IllegalArgumentException if the predicate is null + */ + public static Predicate uniquePredicate() { + return new UniquePredicate(); + } + + /** + * Constructor that performs no validation. + * Use uniquePredicate if you want that. + */ + public UniquePredicate() { + super(); + } + + /** + * Evaluates the predicate returning true if the input object hasn't been + * received yet. + * + * @param object the input object + * @return true if this is the first time the object is seen + */ + public boolean evaluate(final T object) { + return iSet.add(object); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/WhileClosure.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/WhileClosure.java new file mode 100644 index 000000000..dcf2844bf --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/WhileClosure.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.functors; + +import org.apache.commons.collections4.Closure; +import org.apache.commons.collections4.Predicate; + +/** + * Closure implementation that executes a closure repeatedly until a condition is met, + * like a do-while or while loop. + *

              + * WARNING: from v4.1 onwards this class will not be serializable anymore + * in order to prevent potential remote code execution exploits. Please refer to + * COLLECTIONS-580 + * for more details. + * + * @since 3.0 + * @version $Id: WhileClosure.java 1714262 2015-11-13 20:08:45Z tn $ + */ +public class WhileClosure implements Closure { + + /** The test condition */ + private final Predicate iPredicate; + /** The closure to call */ + private final Closure iClosure; + /** The flag, true is a do loop, false is a while */ + private final boolean iDoLoop; + + /** + * Factory method that performs validation. + * + * @param the type that the closure acts on + * @param predicate the predicate used to evaluate when the loop terminates, not null + * @param closure the closure the execute, not null + * @param doLoop true to act as a do-while loop, always executing the closure once + * @return the while closure + * @throws NullPointerException if the predicate or closure is null + */ + public static Closure whileClosure(final Predicate predicate, + final Closure closure, final boolean doLoop) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null"); + } + if (closure == null) { + throw new NullPointerException("Closure must not be null"); + } + return new WhileClosure(predicate, closure, doLoop); + } + + /** + * Constructor that performs no validation. + * Use whileClosure if you want that. + * + * @param predicate the predicate used to evaluate when the loop terminates, not null + * @param closure the closure the execute, not null + * @param doLoop true to act as a do-while loop, always executing the closure once + */ + public WhileClosure(final Predicate predicate, final Closure closure, final boolean doLoop) { + super(); + iPredicate = predicate; + iClosure = closure; + iDoLoop = doLoop; + } + + /** + * Executes the closure until the predicate is false. + * + * @param input the input object + */ + @Override + public void execute(final E input) { + if (iDoLoop) { + iClosure.execute(input); + } + while (iPredicate.evaluate(input)) { + iClosure.execute(input); + } + } + + /** + * Gets the predicate in use. + * + * @return the predicate + * @since 3.1 + */ + public Predicate getPredicate() { + return iPredicate; + } + + /** + * Gets the closure. + * + * @return the closure + * @since 3.1 + */ + public Closure getClosure() { + return iClosure; + } + + /** + * Is the loop a do-while loop. + * + * @return true is do-while, false if while + * @since 3.1 + */ + public boolean isDoLoop() { + return iDoLoop; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/functors/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/functors/package-info.java new file mode 100644 index 000000000..f025519d4 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/functors/package-info.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the + * {@link org.apache.commons.collections4.Closure Closure}, + * {@link org.apache.commons.collections4.Predicate Predicate}, + * {@link org.apache.commons.collections4.Transformer Transformer} and + * {@link org.apache.commons.collections4.Factory Factory} interfaces. + * These provide simple callbacks for processing with collections. + *

              + * WARNING: from v4.1 onwards several unsafe classes in this package + * will not be serializable anymore in order to prevent potential remote + * code execution exploits. + *

              + * Classes considered to be unsafe are: + *

                + *
              • CloneTransformer
              • + *
              • ForClosure
              • + *
              • InstantiateFactory
              • + *
              • InstantiateTransformer
              • + *
              • InvokerTransformer
              • + *
              • PrototypeFactory$PrototypeCloneFactory
              • + *
              • PrototypeFactory$PrototypeSerializationFactory
              • + *
              • WhileClosure
              • + *
              + * + * @version $Id: package-info.java 1714262 2015-11-13 20:08:45Z tn $ + */ +package org.apache.commons.collections4.functors; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractEmptyIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractEmptyIterator.java new file mode 100644 index 000000000..8716cda84 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractEmptyIterator.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.NoSuchElementException; + +/** + * Provides an implementation of an empty iterator. + * + * @since 3.1 + * @version $Id: AbstractEmptyIterator.java 1477802 2013-04-30 20:01:28Z tn $ + */ +abstract class AbstractEmptyIterator { + + /** + * Constructor. + */ + protected AbstractEmptyIterator() { + super(); + } + + public boolean hasNext() { + return false; + } + + public E next() { + throw new NoSuchElementException("Iterator contains no elements"); + } + + public boolean hasPrevious() { + return false; + } + + public E previous() { + throw new NoSuchElementException("Iterator contains no elements"); + } + + public int nextIndex() { + return 0; + } + + public int previousIndex() { + return -1; + } + + public void add(final E obj) { + throw new UnsupportedOperationException("add() not supported for empty Iterator"); + } + + public void set(final E obj) { + throw new IllegalStateException("Iterator contains no elements"); + } + + public void remove() { + throw new IllegalStateException("Iterator contains no elements"); + } + + public void reset() { + // do nothing + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractEmptyMapIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractEmptyMapIterator.java new file mode 100644 index 000000000..d45798a5e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractEmptyMapIterator.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +/** + * Provides an implementation of an empty map iterator. + * + * @since 4.0 + * @version $Id: AbstractEmptyMapIterator.java 1477802 2013-04-30 20:01:28Z tn $ + */ +public abstract class AbstractEmptyMapIterator extends AbstractEmptyIterator { + + /** + * Create a new AbstractEmptyMapIterator. + */ + public AbstractEmptyMapIterator() { + super(); + } + + public K getKey() { + throw new IllegalStateException("Iterator contains no elements"); + } + + public V getValue() { + throw new IllegalStateException("Iterator contains no elements"); + } + + public V setValue(final V value) { + throw new IllegalStateException("Iterator contains no elements"); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractIteratorDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractIteratorDecorator.java new file mode 100644 index 000000000..9b030dba1 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractIteratorDecorator.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; + +/** + * Provides basic behaviour for decorating an iterator with extra functionality. + *

              + * All methods are forwarded to the decorated iterator. + * + * @since 3.0 + * @version $Id: AbstractIteratorDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractIteratorDecorator extends AbstractUntypedIteratorDecorator { + + //----------------------------------------------------------------------- + /** + * Constructor that decorates the specified iterator. + * + * @param iterator the iterator to decorate, must not be null + * @throws NullPointerException if the iterator is null + */ + protected AbstractIteratorDecorator(final Iterator iterator) { + super(iterator); + } + + /** {@inheritDoc} */ + public E next() { + return getIterator().next(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractListIteratorDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractListIteratorDecorator.java new file mode 100644 index 000000000..a0771f0d7 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractListIteratorDecorator.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.ListIterator; + +/** + * Provides basic behaviour for decorating a list iterator with extra functionality. + *

              + * All methods are forwarded to the decorated list iterator. + * + * @since 3.0 + * @version $Id: AbstractListIteratorDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class AbstractListIteratorDecorator implements ListIterator { + + /** The iterator being decorated */ + private final ListIterator iterator; + + //----------------------------------------------------------------------- + /** + * Constructor that decorates the specified iterator. + * + * @param iterator the iterator to decorate, must not be null + * @throws NullPointerException if the iterator is null + */ + public AbstractListIteratorDecorator(final ListIterator iterator) { + super(); + if (iterator == null) { + throw new NullPointerException("ListIterator must not be null"); + } + this.iterator = iterator; + } + + /** + * Gets the iterator being decorated. + * + * @return the decorated iterator + */ + protected ListIterator getListIterator() { + return iterator; + } + + //----------------------------------------------------------------------- + + /** {@inheritDoc} */ + public boolean hasNext() { + return iterator.hasNext(); + } + + /** {@inheritDoc} */ + public E next() { + return iterator.next(); + } + + /** {@inheritDoc} */ + public int nextIndex() { + return iterator.nextIndex(); + } + + /** {@inheritDoc} */ + public boolean hasPrevious() { + return iterator.hasPrevious(); + } + + /** {@inheritDoc} */ + public E previous() { + return iterator.previous(); + } + + /** {@inheritDoc} */ + public int previousIndex() { + return iterator.previousIndex(); + } + + /** {@inheritDoc} */ + public void remove() { + iterator.remove(); + } + + /** {@inheritDoc} */ + public void set(final E obj) { + iterator.set(obj); + } + + /** {@inheritDoc} */ + public void add(final E obj) { + iterator.add(obj); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractMapIteratorDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractMapIteratorDecorator.java new file mode 100644 index 000000000..bc2a23fe8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractMapIteratorDecorator.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import org.apache.commons.collections4.MapIterator; + +/** + * Provides basic behaviour for decorating a map iterator with extra functionality. + *

              + * All methods are forwarded to the decorated map iterator. + * + * @since 3.0 + * @version $Id: AbstractMapIteratorDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class AbstractMapIteratorDecorator implements MapIterator { + + /** The iterator being decorated */ + private final MapIterator iterator; + + //----------------------------------------------------------------------- + /** + * Constructor that decorates the specified iterator. + * + * @param iterator the iterator to decorate, must not be null + * @throws NullPointerException if the iterator is null + */ + public AbstractMapIteratorDecorator(final MapIterator iterator) { + super(); + if (iterator == null) { + throw new NullPointerException("MapIterator must not be null"); + } + this.iterator = iterator; + } + + /** + * Gets the iterator being decorated. + * + * @return the decorated iterator + */ + protected MapIterator getMapIterator() { + return iterator; + } + + //----------------------------------------------------------------------- + + /** {@inheritDoc} */ + public boolean hasNext() { + return iterator.hasNext(); + } + + /** {@inheritDoc} */ + public K next() { + return iterator.next(); + } + + /** {@inheritDoc} */ + public void remove() { + iterator.remove(); + } + + /** {@inheritDoc} */ + public K getKey() { + return iterator.getKey(); + } + + /** {@inheritDoc} */ + public V getValue() { + return iterator.getValue(); + } + + /** {@inheritDoc} */ + public V setValue(final V obj) { + return iterator.setValue(obj); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractOrderedMapIteratorDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractOrderedMapIteratorDecorator.java new file mode 100644 index 000000000..d3d748909 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractOrderedMapIteratorDecorator.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import org.apache.commons.collections4.OrderedMapIterator; + +/** + * Provides basic behaviour for decorating an ordered map iterator with extra functionality. + *

              + * All methods are forwarded to the decorated map iterator. + * + * @since 3.0 + * @version $Id: AbstractOrderedMapIteratorDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class AbstractOrderedMapIteratorDecorator implements OrderedMapIterator { + + /** The iterator being decorated */ + private final OrderedMapIterator iterator; + + //----------------------------------------------------------------------- + /** + * Constructor that decorates the specified iterator. + * + * @param iterator the iterator to decorate, must not be null + * @throws NullPointerException if the iterator is null + */ + public AbstractOrderedMapIteratorDecorator(final OrderedMapIterator iterator) { + super(); + if (iterator == null) { + throw new NullPointerException("OrderedMapIterator must not be null"); + } + this.iterator = iterator; + } + + /** + * Gets the iterator being decorated. + * + * @return the decorated iterator + */ + protected OrderedMapIterator getOrderedMapIterator() { + return iterator; + } + + //----------------------------------------------------------------------- + + /** {@inheritDoc} */ + public boolean hasNext() { + return iterator.hasNext(); + } + + /** {@inheritDoc} */ + public K next() { + return iterator.next(); + } + + /** {@inheritDoc} */ + public boolean hasPrevious() { + return iterator.hasPrevious(); + } + + /** {@inheritDoc} */ + public K previous() { + return iterator.previous(); + } + + /** {@inheritDoc} */ + public void remove() { + iterator.remove(); + } + + /** {@inheritDoc} */ + public K getKey() { + return iterator.getKey(); + } + + /** {@inheritDoc} */ + public V getValue() { + return iterator.getValue(); + } + + /** {@inheritDoc} */ + public V setValue(final V obj) { + return iterator.setValue(obj); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractUntypedIteratorDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractUntypedIteratorDecorator.java new file mode 100644 index 000000000..87d232f2c --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/AbstractUntypedIteratorDecorator.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; + +/** + * Provides basic behaviour for decorating an iterator with extra functionality + * without committing the generic type of the Iterator implementation. + *

              + * All methods are forwarded to the decorated iterator. + * + * @since 4.0 + * @version $Id: AbstractUntypedIteratorDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractUntypedIteratorDecorator implements Iterator { + + /** The iterator being decorated */ + private final Iterator iterator; + + /** + * Create a new AbstractUntypedIteratorDecorator. + * + * @param iterator the iterator to decorate + * @throws NullPointerException if the iterator is null + */ + protected AbstractUntypedIteratorDecorator(final Iterator iterator) { + super(); + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + this.iterator = iterator; + } + + /** + * Gets the iterator being decorated. + * + * @return the decorated iterator + */ + protected Iterator getIterator() { + return iterator; + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public void remove() { + iterator.remove(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ArrayIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ArrayIterator.java new file mode 100644 index 000000000..52ec85bfb --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ArrayIterator.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.lang.reflect.Array; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.ResettableIterator; + +/** + * Implements an {@link java.util.Iterator Iterator} over any array. + *

              + * The array can be either an array of object or of primitives. If you know + * that you have an object array, the + * {@link org.apache.commons.collections4.iterators.ObjectArrayIterator ObjectArrayIterator} + * class is a better choice, as it will perform better. + *

              + * The iterator implements a {@link #reset} method, allowing the reset of + * the iterator back to the start if required. + * + * @since 1.0 + * @version $Id: ArrayIterator.java 1494282 2013-06-18 20:09:15Z sebb $ + */ +public class ArrayIterator implements ResettableIterator { + + /** The array to iterate over */ + final Object array; + /** The start index to loop from */ + final int startIndex; + /** The end index to loop to */ + final int endIndex; + /** The current iterator index */ + int index = 0; + + // Constructors + // ---------------------------------------------------------------------- + /** + * Constructs an ArrayIterator that will iterate over the values in the + * specified array. + * + * @param array the array to iterate over. + * @throws IllegalArgumentException if array is not an array. + * @throws NullPointerException if array is null + */ + public ArrayIterator(final Object array) { + this(array, 0); + } + + /** + * Constructs an ArrayIterator that will iterate over the values in the + * specified array from a specific start index. + * + * @param array the array to iterate over. + * @param startIndex the index to start iterating at. + * @throws IllegalArgumentException if array is not an array. + * @throws NullPointerException if array is null + * @throws IndexOutOfBoundsException if the index is invalid + */ + public ArrayIterator(final Object array, final int startIndex) { + this(array, startIndex, Array.getLength(array)); + } + + /** + * Construct an ArrayIterator that will iterate over a range of values + * in the specified array. + * + * @param array the array to iterate over. + * @param startIndex the index to start iterating at. + * @param endIndex the index to finish iterating at. + * @throws IllegalArgumentException if array is not an array. + * @throws NullPointerException if array is null + * @throws IndexOutOfBoundsException if either index is invalid + */ + public ArrayIterator(final Object array, final int startIndex, final int endIndex) { + super(); + + this.array = array; + this.startIndex = startIndex; + this.endIndex = endIndex; + this.index = startIndex; + + final int len = Array.getLength(array); + checkBound(startIndex, len, "start"); + checkBound(endIndex, len, "end"); + if (endIndex < startIndex) { + throw new IllegalArgumentException("End index must not be less than start index."); + } + } + + /** + * Checks whether the index is valid or not. + * + * @param bound the index to check + * @param len the length of the array + * @param type the index type (for error messages) + * @throws IndexOutOfBoundsException if the index is invalid + */ + protected void checkBound(final int bound, final int len, final String type ) { + if (bound > len) { + throw new ArrayIndexOutOfBoundsException( + "Attempt to make an ArrayIterator that " + type + + "s beyond the end of the array. " + ); + } + if (bound < 0) { + throw new ArrayIndexOutOfBoundsException( + "Attempt to make an ArrayIterator that " + type + + "s before the start of the array. " + ); + } + } + + // Iterator interface + //----------------------------------------------------------------------- + /** + * Returns true if there are more elements to return from the array. + * + * @return true if there is a next element to return + */ + public boolean hasNext() { + return index < endIndex; + } + + /** + * Returns the next element in the array. + * + * @return the next element in the array + * @throws NoSuchElementException if all the elements in the array + * have already been returned + */ + @SuppressWarnings("unchecked") + public E next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + return (E) Array.get(array, index++); + } + + /** + * Throws {@link UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + */ + public void remove() { + throw new UnsupportedOperationException("remove() method is not supported"); + } + + // Properties + //----------------------------------------------------------------------- + /** + * Gets the array that this iterator is iterating over. + * + * @return the array this iterator iterates over. + */ + public Object getArray() { + return array; + } + + /** + * Gets the start index to loop from. + * + * @return the start index + * @since 4.0 + */ + public int getStartIndex() { + return this.startIndex; + } + + /** + * Gets the end index to loop to. + * + * @return the end index + * @since 4.0 + */ + public int getEndIndex() { + return this.endIndex; + } + + /** + * Resets the iterator back to the start index. + */ + public void reset() { + this.index = this.startIndex; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ArrayListIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ArrayListIterator.java new file mode 100644 index 000000000..4d363e4ee --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ArrayListIterator.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.lang.reflect.Array; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.ResettableListIterator; + +/** + * Implements a {@link ListIterator} over an array. + *

              + * The array can be either an array of object or of primitives. If you know + * that you have an object array, the {@link ObjectArrayListIterator} + * class is a better choice, as it will perform better. + * + *

              + * This iterator does not support {@link #add(Object)} or {@link #remove()}, as the array + * cannot be changed in size. The {@link #set(Object)} method is supported however. + * + * @see org.apache.commons.collections4.iterators.ArrayIterator + * @see java.util.Iterator + * @see java.util.ListIterator + * + * @since 3.0 + * @version $Id: ArrayListIterator.java 1543955 2013-11-20 21:23:53Z tn $ + */ +public class ArrayListIterator extends ArrayIterator + implements ResettableListIterator { + + /** + * Holds the index of the last item returned by a call to next() + * or previous(). This is set to -1 if neither method + * has yet been invoked. lastItemIndex is used to to implement + * the {@link #set} method. + */ + private int lastItemIndex = -1; + + // Constructors + // ---------------------------------------------------------------------- + /** + * Constructs an ArrayListIterator that will iterate over the values in the + * specified array. + * + * @param array the array to iterate over + * @throws IllegalArgumentException if array is not an array. + * @throws NullPointerException if array is null + */ + public ArrayListIterator(final Object array) { + super(array); + } + + /** + * Constructs an ArrayListIterator that will iterate over the values in the + * specified array from a specific start index. + * + * @param array the array to iterate over + * @param startIndex the index to start iterating at + * @throws IllegalArgumentException if array is not an array. + * @throws NullPointerException if array is null + * @throws IndexOutOfBoundsException if the start index is out of bounds + */ + public ArrayListIterator(final Object array, final int startIndex) { + super(array, startIndex); + } + + /** + * Construct an ArrayListIterator that will iterate over a range of values + * in the specified array. + * + * @param array the array to iterate over + * @param startIndex the index to start iterating at + * @param endIndex the index (exclusive) to finish iterating at + * @throws IllegalArgumentException if array is not an array. + * @throws IndexOutOfBoundsException if the start or end index is out of bounds + * @throws IllegalArgumentException if end index is before the start + * @throws NullPointerException if array is null + */ + public ArrayListIterator(final Object array, final int startIndex, final int endIndex) { + super(array, startIndex, endIndex); + } + + // ListIterator interface + //----------------------------------------------------------------------- + /** + * Returns true if there are previous elements to return from the array. + * + * @return true if there is a previous element to return + */ + public boolean hasPrevious() { + return this.index > this.startIndex; + } + + /** + * Gets the previous element from the array. + * + * @return the previous element + * @throws NoSuchElementException if there is no previous element + */ + @SuppressWarnings("unchecked") + public E previous() { + if (hasPrevious() == false) { + throw new NoSuchElementException(); + } + this.lastItemIndex = --this.index; + return (E) Array.get(this.array, this.index); + } + + /** + * Gets the next element from the array. + * + * @return the next element + * @throws NoSuchElementException if there is no next element + */ + @Override + @SuppressWarnings("unchecked") + public E next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + this.lastItemIndex = this.index; + return (E) Array.get(this.array, this.index++); + } + + /** + * Gets the next index to be retrieved. + * + * @return the index of the item to be retrieved next + */ + public int nextIndex() { + return this.index - this.startIndex; + } + + /** + * Gets the index of the item to be retrieved if {@link #previous()} is called. + * + * @return the index of the item to be retrieved next + */ + public int previousIndex() { + return this.index - this.startIndex - 1; + } + + /** + * This iterator does not support modification of its backing collection, and so will + * always throw an {@link UnsupportedOperationException} when this method is invoked. + * + * @param o the element to add + * @throws UnsupportedOperationException always thrown. + * @see java.util.ListIterator#set + */ + public void add(final Object o) { + throw new UnsupportedOperationException("add() method is not supported"); + } + + /** + * Sets the element under the cursor. + *

              + * This method sets the element that was returned by the last call + * to {@link #next()} of {@link #previous()}. + *

              + * Note: {@link ListIterator} implementations that support + * add() and remove() only allow set() to be called + * once per call to next() or previous (see the {@link ListIterator} + * javadoc for more details). Since this implementation does + * not support add() or remove(), set() may be + * called as often as desired. + * + * @param o the element to set + * @throws IllegalStateException if {@link #next()} or {@link #previous()} has not been called + * before {@link #set(Object)} + * @see java.util.ListIterator#set + */ + public void set(final Object o) { + if (this.lastItemIndex == -1) { + throw new IllegalStateException("must call next() or previous() before a call to set()"); + } + + Array.set(this.array, this.lastItemIndex, o); + } + + /** + * Resets the iterator back to the start index. + */ + @Override + public void reset() { + super.reset(); + this.lastItemIndex = -1; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/BoundedIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/BoundedIterator.java new file mode 100644 index 000000000..16973bccc --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/BoundedIterator.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law + * or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Decorates another iterator to return elements in a specific range. + *

              + * The decorated iterator is bounded in the range [offset, offset+max). + * The {@code offset} corresponds to the position of the first element to + * be returned from the decorated iterator, and {@code max} is the maximum + * number of elements to be returned at most. + *

              + * In case an offset parameter other than 0 is provided, the decorated + * iterator is immediately advanced to this position, skipping all elements + * before that position. + * + * @since 4.1 + * @version $Id: BoundedIterator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class BoundedIterator implements Iterator { + + /** The iterator being decorated. */ + private final Iterator iterator; + + /** The offset to bound the first element return */ + private final long offset; + + /** The max number of elements to return */ + private final long max; + + /** The position of the current element */ + private long pos; + + //----------------------------------------------------------------------- + + /** + * Decorates the specified iterator to return at most the given number of elements, + * skipping all elements until the iterator reaches the position at {@code offset}. + *

              + * The iterator is immediately advanced until it reaches the position at {@code offset}, + * incurring O(n) time. + * + * @param iterator the iterator to be decorated + * @param offset the index of the first element of the decorated iterator to return + * @param max the maximum number of elements of the decorated iterator to return + * @throws NullPointerException if iterator is null + * @throws IllegalArgumentException if either offset or max is negative + */ + public BoundedIterator(final Iterator iterator, final long offset, final long max) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + if (offset < 0) { + throw new IllegalArgumentException("Offset parameter must not be negative."); + } + if (max < 0) { + throw new IllegalArgumentException("Max parameter must not be negative."); + } + + this.iterator = iterator; + this.offset = offset; + this.max = max; + pos = 0; + init(); + } + + /** + * Advances the underlying iterator to the beginning of the bounded range. + */ + private void init() { + while (pos < offset && iterator.hasNext()) { + iterator.next(); + pos++; + } + } + + //----------------------------------------------------------------------- + + public boolean hasNext() { + if (!checkBounds()) { + return false; + } + return iterator.hasNext(); + } + + /** + * Checks whether the iterator is still within its bounded range. + * @return {@code true} if the iterator is within its bounds, {@code false} otherwise + */ + private boolean checkBounds() { + if (pos - offset + 1 > max) { + return false; + } + return true; + } + + public E next() { + if (!checkBounds()) { + throw new NoSuchElementException(); + } + final E next = iterator.next(); + pos++; + return next; + } + + /** + * {@inheritDoc} + *

              + * In case an offset other than 0 was specified, the underlying iterator will be advanced + * to this position upon creation. A call to {@link #remove()} will still result in an + * {@link IllegalStateException} if no explicit call to {@link #next()} has been made prior + * to calling {@link #remove()}. + */ + public void remove() { + if (pos <= offset) { + throw new IllegalStateException("remove() can not be called before calling next()"); + } + iterator.remove(); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/CollatingIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/CollatingIterator.java new file mode 100644 index 000000000..e8649a40a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/CollatingIterator.java @@ -0,0 +1,397 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.list.UnmodifiableList; + + +/** + * Provides an ordered iteration over the elements contained in a collection of + * ordered Iterators. + *

              + * Given two ordered {@link Iterator} instances A and + * B, the {@link #next} method on this iterator will return the + * lesser of A.next() and B.next(). + * + * @since 2.1 + * @version $Id: CollatingIterator.java 1683632 2015-06-04 20:38:03Z tn $ + */ +public class CollatingIterator implements Iterator { + + /** The {@link Comparator} used to evaluate order. */ + private Comparator comparator = null; + + /** The list of {@link Iterator}s to evaluate. */ + private List> iterators = null; + + /** {@link Iterator#next Next} objects peeked from each iterator. */ + private List values = null; + + /** Whether or not each {@link #values} element has been set. */ + private BitSet valueSet = null; + + /** + * Index of the {@link #iterators iterator} from whom the last returned + * value was obtained. + */ + private int lastReturned = -1; + + // Constructors + // ---------------------------------------------------------------------- + /** + * Constructs a new CollatingIterator. A comparator must be + * set by calling {@link #setComparator(Comparator)} before invoking + * {@link #hasNext()}, or {@link #next()} for the first time. Child + * iterators will have to be manually added using the + * {@link #addIterator(Iterator)} method. + */ + public CollatingIterator() { + this(null, 2); + } + + /** + * Constructs a new CollatingIterator that will used the + * specified comparator for ordering. Child iterators will have to be + * manually added using the {@link #addIterator(Iterator)} method. + * + * @param comp the comparator to use to sort; must not be null, + * unless you'll be invoking {@link #setComparator(Comparator)} later on. + */ + public CollatingIterator(final Comparator comp) { + this(comp, 2); + } + + /** + * Constructs a new CollatingIterator that will used the + * specified comparator for ordering and have the specified initial + * capacity. Child iterators will have to be manually added using the + * {@link #addIterator(Iterator)} method. + * + * @param comp the comparator to use to sort; must not be null, + * unless you'll be invoking {@link #setComparator(Comparator)} later on. + * @param initIterCapacity the initial capacity for the internal list of + * child iterators + */ + public CollatingIterator(final Comparator comp, final int initIterCapacity) { + iterators = new ArrayList>(initIterCapacity); + setComparator(comp); + } + + /** + * Constructs a new CollatingIterator that will use the + * specified comparator to provide ordered iteration over the two given + * iterators. + * + * @param comp the comparator to use to sort; must not be null, + * unless you'll be invoking {@link #setComparator(Comparator)} later on. + * @param a the first child ordered iterator + * @param b the second child ordered iterator + * @throws NullPointerException if either iterator is null + */ + public CollatingIterator(final Comparator comp, final Iterator a, + final Iterator b) { + this(comp, 2); + addIterator(a); + addIterator(b); + } + + /** + * Constructs a new CollatingIterator that will use the + * specified comparator to provide ordered iteration over the array of + * iterators. + * + * @param comp the comparator to use to sort; must not be null, + * unless you'll be invoking {@link #setComparator(Comparator)} later on. + * @param iterators the array of iterators + * @throws NullPointerException if iterators array is or contains null + */ + public CollatingIterator(final Comparator comp, final Iterator[] iterators) { + this(comp, iterators.length); + for (final Iterator iterator : iterators) { + addIterator(iterator); + } + } + + /** + * Constructs a new CollatingIterator that will use the + * specified comparator to provide ordered iteration over the collection of + * iterators. + * + * @param comp the comparator to use to sort; must not be null, + * unless you'll be invoking {@link #setComparator(Comparator)} later on. + * @param iterators the collection of iterators + * @throws NullPointerException if the iterators collection is or contains null + * @throws ClassCastException if the iterators collection contains an + * element that's not an {@link Iterator} + */ + public CollatingIterator(final Comparator comp, final Collection> iterators) { + this(comp, iterators.size()); + for (final Iterator iterator : iterators) { + addIterator(iterator); + } + } + + // Public Methods + // ---------------------------------------------------------------------- + /** + * Adds the given {@link Iterator} to the iterators being collated. + * + * @param iterator the iterator to add to the collation, must not be null + * @throws IllegalStateException if iteration has started + * @throws NullPointerException if the iterator is null + */ + public void addIterator(final Iterator iterator) { + checkNotStarted(); + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + iterators.add(iterator); + } + + /** + * Sets the iterator at the given index. + * + * @param index index of the Iterator to replace + * @param iterator Iterator to place at the given index + * @throws IndexOutOfBoundsException if index < 0 or index > size() + * @throws IllegalStateException if iteration has started + * @throws NullPointerException if the iterator is null + */ + public void setIterator(final int index, final Iterator iterator) { + checkNotStarted(); + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + iterators.set(index, iterator); + } + + /** + * Gets the list of Iterators (unmodifiable). + * + * @return the unmodifiable list of iterators added + */ + public List> getIterators() { + return UnmodifiableList.unmodifiableList(iterators); + } + + /** + * Gets the {@link Comparator} by which collatation occurs. + * + * @return the {@link Comparator} + */ + public Comparator getComparator() { + return comparator; + } + + /** + * Sets the {@link Comparator} by which collation occurs. If you + * would like to use the natural sort order (or, in other words, + * if the elements in the iterators are implementing the + * {@link java.lang.Comparable} interface), then use the + * {@link org.apache.commons.collections4.comparators.ComparableComparator}. + * + * @param comp the {@link Comparator} to set + * @throws IllegalStateException if iteration has started + */ + public void setComparator(final Comparator comp) { + checkNotStarted(); + comparator = comp; + } + + // Iterator Methods + // ------------------------------------------------------------------- + /** + * Returns true if any child iterator has remaining elements. + * + * @return true if this iterator has remaining elements + */ + public boolean hasNext() { + start(); + return anyValueSet(valueSet) || anyHasNext(iterators); + } + + /** + * Returns the next ordered element from a child iterator. + * + * @return the next ordered element + * @throws NoSuchElementException if no child iterator has any more elements + */ + public E next() throws NoSuchElementException { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + final int leastIndex = least(); + if (leastIndex == -1) { + throw new NoSuchElementException(); + } + final E val = values.get(leastIndex); + clear(leastIndex); + lastReturned = leastIndex; + return val; + } + + /** + * Removes the last returned element from the child iterator that produced it. + * + * @throws IllegalStateException if there is no last returned element, or if + * the last returned element has already been removed + */ + public void remove() { + if (lastReturned == -1) { + throw new IllegalStateException("No value can be removed at present"); + } + iterators.get(lastReturned).remove(); + } + + /** + * Returns the index of the iterator that returned the last element. + * + * @return the index of the iterator that returned the last element + * @throws IllegalStateException if there is no last returned element + */ + public int getIteratorIndex() { + if (lastReturned == -1) { + throw new IllegalStateException("No value has been returned yet"); + } + + return lastReturned; + } + + // Private Methods + // ------------------------------------------------------------------- + /** + * Initializes the collating state if it hasn't been already. + */ + private void start() { + if (values == null) { + values = new ArrayList(iterators.size()); + valueSet = new BitSet(iterators.size()); + for (int i = 0; i < iterators.size(); i++) { + values.add(null); + valueSet.clear(i); + } + } + } + + /** + * Sets the {@link #values} and {@link #valueSet} attributes at position + * i to the next value of the {@link #iterators iterator} at position + * i, or clear them if the ith iterator has no next + * value. + * + * @return {@code false} iff there was no value to set + */ + private boolean set(final int i) { + final Iterator it = iterators.get(i); + if (it.hasNext()) { + values.set(i, it.next()); + valueSet.set(i); + return true; + } + values.set(i, null); + valueSet.clear(i); + return false; + } + + /** + * Clears the {@link #values} and {@link #valueSet} attributes at position + * i. + */ + private void clear(final int i) { + values.set(i, null); + valueSet.clear(i); + } + + /** + * Throws {@link IllegalStateException} if iteration has started via + * {@link #start}. + * + * @throws IllegalStateException if iteration started + */ + private void checkNotStarted() throws IllegalStateException { + if (values != null) { + throw new IllegalStateException("Can't do that after next or hasNext has been called."); + } + } + + /** + * Returns the index of the least element in {@link #values}, + * {@link #set(int) setting} any uninitialized values. + * + * @throws NullPointerException if no comparator is set + */ + private int least() { + int leastIndex = -1; + E leastObject = null; + for (int i = 0; i < values.size(); i++) { + if (valueSet.get(i) == false) { + set(i); + } + if (valueSet.get(i)) { + if (leastIndex == -1) { + leastIndex = i; + leastObject = values.get(i); + } else { + final E curObject = values.get(i); + if (comparator == null) { + throw new NullPointerException("You must invoke setComparator() to set a comparator first."); + } + if (comparator.compare(curObject, leastObject) < 0) { + leastObject = curObject; + leastIndex = i; + } + } + } + } + return leastIndex; + } + + /** + * Returns true iff any bit in the given set is + * true. + */ + private boolean anyValueSet(final BitSet set) { + for (int i = 0; i < set.size(); i++) { + if (set.get(i)) { + return true; + } + } + return false; + } + + /** + * Returns true iff any {@link Iterator} in the given list has + * a next value. + */ + private boolean anyHasNext(final List> iters) { + for (final Iterator iterator : iters) { + if (iterator.hasNext()) { + return true; + } + } + return false; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyIterator.java new file mode 100644 index 000000000..8e19bb51d --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyIterator.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; + +import org.apache.commons.collections4.ResettableIterator; + +/** + * Provides an implementation of an empty iterator. + *

              + * This class provides an implementation of an empty iterator. + * This class provides for binary compatibility between Commons Collections + * 2.1.1 and 3.1 due to issues with IteratorUtils. + * + * @since 2.1.1 and 3.1 + * @version $Id: EmptyIterator.java 1543955 2013-11-20 21:23:53Z tn $ + */ +public class EmptyIterator extends AbstractEmptyIterator implements ResettableIterator { + + /** + * Singleton instance of the iterator. + * @since 3.1 + */ + @SuppressWarnings("rawtypes") + public static final ResettableIterator RESETTABLE_INSTANCE = new EmptyIterator(); + + /** + * Singleton instance of the iterator. + * @since 2.1.1 and 3.1 + */ + @SuppressWarnings("rawtypes") + public static final Iterator INSTANCE = RESETTABLE_INSTANCE; + + /** + * Get a typed resettable empty iterator instance. + * @param the element type + * @return ResettableIterator + */ + @SuppressWarnings("unchecked") + public static ResettableIterator resettableEmptyIterator() { + return (ResettableIterator) RESETTABLE_INSTANCE; + } + + /** + * Get a typed empty iterator instance. + * @param the element type + * @return Iterator + */ + @SuppressWarnings("unchecked") + public static Iterator emptyIterator() { + return (Iterator) INSTANCE; + } + + /** + * Constructor. + */ + protected EmptyIterator() { + super(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyListIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyListIterator.java new file mode 100644 index 000000000..c0e961974 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyListIterator.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.ListIterator; + +import org.apache.commons.collections4.ResettableListIterator; + +/** + * Provides an implementation of an empty list iterator. + *

              + * This class provides an implementation of an empty list iterator. This class + * provides for binary compatibility between Commons Collections 2.1.1 and 3.1 + * due to issues with IteratorUtils. + * + * @since 2.1.1 and 3.1 + * @version $Id: EmptyListIterator.java 1543955 2013-11-20 21:23:53Z tn $ + */ +public class EmptyListIterator extends AbstractEmptyIterator implements + ResettableListIterator { + + /** + * Singleton instance of the iterator. + * @since 3.1 + */ + @SuppressWarnings("rawtypes") + public static final ResettableListIterator RESETTABLE_INSTANCE = new EmptyListIterator(); + + /** + * Singleton instance of the iterator. + * @since 2.1.1 and 3.1 + */ + @SuppressWarnings("rawtypes") + public static final ListIterator INSTANCE = RESETTABLE_INSTANCE; + + /** + * Get a typed instance of the iterator. + * @param the element type + * @return {@link ResettableListIterator} + */ + @SuppressWarnings("unchecked") + public static ResettableListIterator resettableEmptyListIterator() { + return (ResettableListIterator) RESETTABLE_INSTANCE; + } + + /** + * Get a typed instance of the iterator. + * @param the element type + * @return {@link ListIterator} + */ + @SuppressWarnings("unchecked") + public static ListIterator emptyListIterator() { + return (ListIterator) INSTANCE; + } + + /** + * Constructor. + */ + protected EmptyListIterator() { + super(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyMapIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyMapIterator.java new file mode 100644 index 000000000..6ec8ed83e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyMapIterator.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.ResettableIterator; + +/** + * Provides an implementation of an empty map iterator. + * + * @since 3.1 + * @version $Id: EmptyMapIterator.java 1543955 2013-11-20 21:23:53Z tn $ + */ +public class EmptyMapIterator extends AbstractEmptyMapIterator implements + MapIterator, ResettableIterator { + + /** + * Singleton instance of the iterator. + * @since 3.1 + */ + @SuppressWarnings("rawtypes") + public static final MapIterator INSTANCE = new EmptyMapIterator(); + + /** + * Get a typed instance of the iterator. + * @param the key type + * @param the value type + * @return {@link MapIterator} + */ + @SuppressWarnings("unchecked") + public static MapIterator emptyMapIterator() { + return (MapIterator) INSTANCE; + } + + /** + * Constructor. + */ + protected EmptyMapIterator() { + super(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyOrderedIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyOrderedIterator.java new file mode 100644 index 000000000..8fbcca22f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyOrderedIterator.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import org.apache.commons.collections4.OrderedIterator; +import org.apache.commons.collections4.ResettableIterator; + +/** + * Provides an implementation of an empty ordered iterator. + * + * @since 3.1 + * @version $Id: EmptyOrderedIterator.java 1543955 2013-11-20 21:23:53Z tn $ + */ +public class EmptyOrderedIterator extends AbstractEmptyIterator + implements OrderedIterator, ResettableIterator { + + /** + * Singleton instance of the iterator. + * @since 3.1 + */ + @SuppressWarnings("rawtypes") + public static final OrderedIterator INSTANCE = new EmptyOrderedIterator(); + + /** + * Typed instance of the iterator. + * @param the element type + * @return OrderedIterator + */ + @SuppressWarnings("unchecked") + public static OrderedIterator emptyOrderedIterator() { + return (OrderedIterator) INSTANCE; + } + + /** + * Constructor. + */ + protected EmptyOrderedIterator() { + super(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyOrderedMapIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyOrderedMapIterator.java new file mode 100644 index 000000000..614e602c8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EmptyOrderedMapIterator.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.ResettableIterator; + +/** + * Provides an implementation of an empty ordered map iterator. + * + * @since 3.1 + * @version $Id: EmptyOrderedMapIterator.java 1543955 2013-11-20 21:23:53Z tn $ + */ +public class EmptyOrderedMapIterator extends AbstractEmptyMapIterator + implements OrderedMapIterator, ResettableIterator { + + /** + * Singleton instance of the iterator. + * @since 3.1 + */ + @SuppressWarnings("rawtypes") + public static final OrderedMapIterator INSTANCE = new EmptyOrderedMapIterator(); + + /** + * Get a typed instance of the iterator. + * @param the key type + * @param the value type + * @return {@link OrderedMapIterator} + */ + @SuppressWarnings("unchecked") + public static OrderedMapIterator emptyOrderedMapIterator() { + return (OrderedMapIterator) INSTANCE; + } + + /** + * Constructor. + */ + protected EmptyOrderedMapIterator() { + super(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EntrySetMapIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EntrySetMapIterator.java new file mode 100644 index 000000000..bb9cdc8e0 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EntrySetMapIterator.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.ResettableIterator; + +/** + * Implements a MapIterator using a Map entrySet. + * Reverse iteration is not supported. + *
              + * MapIterator it = map.mapIterator();
              + * while (it.hasNext()) {
              + *   Object key = it.next();
              + *   Object value = it.getValue();
              + *   it.setValue(newValue);
              + * }
              + * 
              + * + * @since 3.0 + * @version $Id: EntrySetMapIterator.java 1477802 2013-04-30 20:01:28Z tn $ + */ +public class EntrySetMapIterator implements MapIterator, ResettableIterator { + + private final Map map; + private Iterator> iterator; + private Map.Entry last; + private boolean canRemove = false; + + /** + * Constructor. + * + * @param map the map to iterate over + */ + public EntrySetMapIterator(final Map map) { + super(); + this.map = map; + this.iterator = map.entrySet().iterator(); + } + + //----------------------------------------------------------------------- + /** + * Checks to see if there are more entries still to be iterated. + * + * @return true if the iterator has more elements + */ + public boolean hasNext() { + return iterator.hasNext(); + } + + /** + * Gets the next key from the Map. + * + * @return the next key in the iteration + * @throws java.util.NoSuchElementException if the iteration is finished + */ + public K next() { + last = iterator.next(); + canRemove = true; + return last.getKey(); + } + + //----------------------------------------------------------------------- + /** + * Removes the last returned key from the underlying Map. + *

              + * This method can be called once per call to next(). + * + * @throws UnsupportedOperationException if remove is not supported by the map + * @throws IllegalStateException if next() has not yet been called + * @throws IllegalStateException if remove() has already been called + * since the last call to next() + */ + public void remove() { + if (canRemove == false) { + throw new IllegalStateException("Iterator remove() can only be called once after next()"); + } + iterator.remove(); + last = null; + canRemove = false; + } + + //----------------------------------------------------------------------- + /** + * Gets the current key, which is the key returned by the last call + * to next(). + * + * @return the current key + * @throws IllegalStateException if next() has not yet been called + */ + public K getKey() { + if (last == null) { + throw new IllegalStateException("Iterator getKey() can only be called after next() and before remove()"); + } + return last.getKey(); + } + + /** + * Gets the current value, which is the value associated with the last key + * returned by next(). + * + * @return the current value + * @throws IllegalStateException if next() has not yet been called + */ + public V getValue() { + if (last == null) { + throw new IllegalStateException("Iterator getValue() can only be called after next() and before remove()"); + } + return last.getValue(); + } + + /** + * Sets the value associated with the current key. + * + * @param value the new value + * @return the previous value + * @throws UnsupportedOperationException if setValue is not supported by the map + * @throws IllegalStateException if next() has not yet been called + * @throws IllegalStateException if remove() has been called since the + * last call to next() + */ + public V setValue(final V value) { + if (last == null) { + throw new IllegalStateException("Iterator setValue() can only be called after next() and before remove()"); + } + return last.setValue(value); + } + + //----------------------------------------------------------------------- + /** + * Resets the state of the iterator. + */ + public void reset() { + iterator = map.entrySet().iterator(); + last = null; + canRemove = false; + } + + /** + * Gets the iterator as a String. + * + * @return a string version of the iterator + */ + @Override + public String toString() { + if (last != null) { + return "MapIterator[" + getKey() + "=" + getValue() + "]"; + } + return "MapIterator[]"; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EnumerationIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EnumerationIterator.java new file mode 100644 index 000000000..8675de9ba --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/EnumerationIterator.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; + +/** + * Adapter to make {@link Enumeration Enumeration} instances appear + * to be {@link Iterator Iterator} instances. + * + * @since 1.0 + * @version $Id: EnumerationIterator.java 1477802 2013-04-30 20:01:28Z tn $ + */ +public class EnumerationIterator implements Iterator { + + /** The collection to remove elements from */ + private final Collection collection; + /** The enumeration being converted */ + private Enumeration enumeration; + /** The last object retrieved */ + private E last; + + // Constructors + //----------------------------------------------------------------------- + /** + * Constructs a new EnumerationIterator that will not + * function until {@link #setEnumeration(Enumeration)} is called. + */ + public EnumerationIterator() { + this(null, null); + } + + /** + * Constructs a new EnumerationIterator that provides + * an iterator view of the given enumeration. + * + * @param enumeration the enumeration to use + */ + public EnumerationIterator(final Enumeration enumeration) { + this(enumeration, null); + } + + /** + * Constructs a new EnumerationIterator that will remove + * elements from the specified collection. + * + * @param enumeration the enumeration to use + * @param collection the collection to remove elements from + */ + public EnumerationIterator(final Enumeration enumeration, final Collection collection) { + super(); + this.enumeration = enumeration; + this.collection = collection; + this.last = null; + } + + // Iterator interface + //----------------------------------------------------------------------- + /** + * Returns true if the underlying enumeration has more elements. + * + * @return true if the underlying enumeration has more elements + * @throws NullPointerException if the underlying enumeration is null + */ + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + + /** + * Returns the next object from the enumeration. + * + * @return the next object from the enumeration + * @throws NullPointerException if the enumeration is null + */ + public E next() { + last = enumeration.nextElement(); + return last; + } + + /** + * Removes the last retrieved element if a collection is attached. + *

              + * Functions if an associated Collection is known. + * If so, the first occurrence of the last returned object from this + * iterator will be removed from the collection. + * + * @exception IllegalStateException next() not called. + * @exception UnsupportedOperationException if no associated collection + */ + public void remove() { + if (collection != null) { + if (last != null) { + collection.remove(last); + } else { + throw new IllegalStateException("next() must have been called for remove() to function"); + } + } else { + throw new UnsupportedOperationException("No Collection associated with this Iterator"); + } + } + + // Properties + //----------------------------------------------------------------------- + /** + * Returns the underlying enumeration. + * + * @return the underlying enumeration + */ + public Enumeration getEnumeration() { + return enumeration; + } + + /** + * Sets the underlying enumeration. + * + * @param enumeration the new underlying enumeration + */ + public void setEnumeration(final Enumeration enumeration) { + this.enumeration = enumeration; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/FilterIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/FilterIterator.java new file mode 100644 index 000000000..a58f4ac99 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/FilterIterator.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.Predicate; + +/** + * Decorates another {@link Iterator} using a predicate to filter elements. + *

              + * This iterator decorates the underlying iterator, only allowing through + * those elements that match the specified {@link Predicate Predicate}. + * + * @since 1.0 + * @version $Id: FilterIterator.java 1477802 2013-04-30 20:01:28Z tn $ + */ +public class FilterIterator implements Iterator { + + /** The iterator being used */ + private Iterator iterator; + /** The predicate being used */ + private Predicate predicate; + /** The next object in the iteration */ + private E nextObject; + /** Whether the next object has been calculated yet */ + private boolean nextObjectSet = false; + + //----------------------------------------------------------------------- + /** + * Constructs a new FilterIterator that will not function + * until {@link #setIterator(Iterator) setIterator} is invoked. + */ + public FilterIterator() { + super(); + } + + /** + * Constructs a new FilterIterator that will not function + * until {@link #setPredicate(Predicate) setPredicate} is invoked. + * + * @param iterator the iterator to use + */ + public FilterIterator(final Iterator iterator) { + super(); + this.iterator = iterator; + } + + /** + * Constructs a new FilterIterator that will use the + * given iterator and predicate. + * + * @param iterator the iterator to use + * @param predicate the predicate to use + */ + public FilterIterator(final Iterator iterator, final Predicate predicate) { + super(); + this.iterator = iterator; + this.predicate = predicate; + } + + //----------------------------------------------------------------------- + /** + * Returns true if the underlying iterator contains an object that + * matches the predicate. + * + * @return true if there is another object that matches the predicate + * @throws NullPointerException if either the iterator or predicate are null + */ + public boolean hasNext() { + return nextObjectSet || setNextObject(); + } + + /** + * Returns the next object that matches the predicate. + * + * @return the next object which matches the given predicate + * @throws NullPointerException if either the iterator or predicate are null + * @throws NoSuchElementException if there are no more elements that + * match the predicate + */ + public E next() { + if (!nextObjectSet) { + if (!setNextObject()) { + throw new NoSuchElementException(); + } + } + nextObjectSet = false; + return nextObject; + } + + /** + * Removes from the underlying collection of the base iterator the last + * element returned by this iterator. + * This method can only be called + * if next() was called, but not after + * hasNext(), because the hasNext() call + * changes the base iterator. + * + * @throws IllegalStateException if hasNext() has already + * been called. + */ + public void remove() { + if (nextObjectSet) { + throw new IllegalStateException("remove() cannot be called"); + } + iterator.remove(); + } + + //----------------------------------------------------------------------- + /** + * Gets the iterator this iterator is using. + * + * @return the iterator + */ + public Iterator getIterator() { + return iterator; + } + + /** + * Sets the iterator for this iterator to use. + * If iteration has started, this effectively resets the iterator. + * + * @param iterator the iterator to use + */ + public void setIterator(final Iterator iterator) { + this.iterator = iterator; + nextObject = null; + nextObjectSet = false; + } + + //----------------------------------------------------------------------- + /** + * Gets the predicate this iterator is using. + * + * @return the predicate + */ + public Predicate getPredicate() { + return predicate; + } + + /** + * Sets the predicate this the iterator to use. + * + * @param predicate the predicate to use + */ + public void setPredicate(final Predicate predicate) { + this.predicate = predicate; + nextObject = null; + nextObjectSet = false; + } + + //----------------------------------------------------------------------- + /** + * Set nextObject to the next object. If there are no more + * objects then return false. Otherwise, return true. + */ + private boolean setNextObject() { + while (iterator.hasNext()) { + final E object = iterator.next(); + if (predicate.evaluate(object)) { + nextObject = object; + nextObjectSet = true; + return true; + } + } + return false; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/FilterListIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/FilterListIterator.java new file mode 100644 index 000000000..ffa25137f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/FilterListIterator.java @@ -0,0 +1,274 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.Predicate; + +/** + * Decorates another {@link ListIterator} using a predicate to filter elements. + *

              + * This iterator decorates the underlying iterator, only allowing through + * those elements that match the specified {@link Predicate Predicate}. + * + * @since 2.0 + * @version $Id: FilterListIterator.java 1477802 2013-04-30 20:01:28Z tn $ + */ +public class FilterListIterator implements ListIterator { + + /** The iterator being used */ + private ListIterator iterator; + + /** The predicate being used */ + private Predicate predicate; + + /** + * The value of the next (matching) object, when + * {@link #nextObjectSet} is true. + */ + private E nextObject; + + /** + * Whether or not the {@link #nextObject} has been set + * (possibly to null). + */ + private boolean nextObjectSet = false; + + /** + * The value of the previous (matching) object, when + * {@link #previousObjectSet} is true. + */ + private E previousObject; + + /** + * Whether or not the {@link #previousObject} has been set + * (possibly to null). + */ + private boolean previousObjectSet = false; + + /** + * The index of the element that would be returned by {@link #next}. + */ + private int nextIndex = 0; + + //----------------------------------------------------------------------- + /** + * Constructs a new FilterListIterator that will not function + * until {@link #setListIterator(ListIterator) setListIterator} + * and {@link #setPredicate(Predicate) setPredicate} are invoked. + */ + public FilterListIterator() { + super(); + } + + /** + * Constructs a new FilterListIterator that will not + * function until {@link #setPredicate(Predicate) setPredicate} is invoked. + * + * @param iterator the iterator to use + */ + public FilterListIterator(final ListIterator iterator ) { + super(); + this.iterator = iterator; + } + + /** + * Constructs a new FilterListIterator. + * + * @param iterator the iterator to use + * @param predicate the predicate to use + */ + public FilterListIterator(final ListIterator iterator, final Predicate predicate) { + super(); + this.iterator = iterator; + this.predicate = predicate; + } + + /** + * Constructs a new FilterListIterator that will not function + * until {@link #setListIterator(ListIterator) setListIterator} is invoked. + * + * @param predicate the predicate to use. + */ + public FilterListIterator(final Predicate predicate) { + super(); + this.predicate = predicate; + } + + //----------------------------------------------------------------------- + /** Not supported. */ + public void add(final E o) { + throw new UnsupportedOperationException("FilterListIterator.add(Object) is not supported."); + } + + public boolean hasNext() { + return nextObjectSet || setNextObject(); + } + + public boolean hasPrevious() { + return previousObjectSet || setPreviousObject(); + } + + public E next() { + if (!nextObjectSet) { + if (!setNextObject()) { + throw new NoSuchElementException(); + } + } + nextIndex++; + final E temp = nextObject; + clearNextObject(); + return temp; + } + + public int nextIndex() { + return nextIndex; + } + + public E previous() { + if (!previousObjectSet) { + if (!setPreviousObject()) { + throw new NoSuchElementException(); + } + } + nextIndex--; + final E temp = previousObject; + clearPreviousObject(); + return temp; + } + + public int previousIndex() { + return nextIndex-1; + } + + /** Not supported. */ + public void remove() { + throw new UnsupportedOperationException("FilterListIterator.remove() is not supported."); + } + + /** Not supported. */ + public void set(final E o) { + throw new UnsupportedOperationException("FilterListIterator.set(Object) is not supported."); + } + + //----------------------------------------------------------------------- + /** + * Gets the iterator this iterator is using. + * + * @return the iterator. + */ + public ListIterator getListIterator() { + return iterator; + } + + /** + * Sets the iterator for this iterator to use. + * If iteration has started, this effectively resets the iterator. + * + * @param iterator the iterator to use + */ + public void setListIterator(final ListIterator iterator) { + this.iterator = iterator; + } + + //----------------------------------------------------------------------- + /** + * Gets the predicate this iterator is using. + * + * @return the predicate. + */ + public Predicate getPredicate() { + return predicate; + } + + /** + * Sets the predicate this the iterator to use. + * + * @param predicate the transformer to use + */ + public void setPredicate(final Predicate predicate) { + this.predicate = predicate; + } + + //----------------------------------------------------------------------- + private void clearNextObject() { + nextObject = null; + nextObjectSet = false; + } + + private boolean setNextObject() { + // if previousObjectSet, + // then we've walked back one step in the + // underlying list (due to a hasPrevious() call) + // so skip ahead one matching object + if (previousObjectSet) { + clearPreviousObject(); + if (!setNextObject()) { + return false; + } + clearNextObject(); + } + + if (iterator == null) { + return false; + } + while (iterator.hasNext()) { + final E object = iterator.next(); + if (predicate.evaluate(object)) { + nextObject = object; + nextObjectSet = true; + return true; + } + } + return false; + } + + private void clearPreviousObject() { + previousObject = null; + previousObjectSet = false; + } + + private boolean setPreviousObject() { + // if nextObjectSet, + // then we've walked back one step in the + // underlying list (due to a hasNext() call) + // so skip ahead one matching object + if (nextObjectSet) { + clearNextObject(); + if (!setPreviousObject()) { + return false; + } + clearPreviousObject(); + } + + if (iterator == null) { + return false; + } + while (iterator.hasPrevious()) { + final E object = iterator.previous(); + if (predicate.evaluate(object)) { + previousObject = object; + previousObjectSet = true; + return true; + } + } + return false; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorChain.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorChain.java new file mode 100644 index 000000000..6b58b4e46 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorChain.java @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Queue; + +/** + * An IteratorChain is an Iterator that wraps a number of Iterators. + *

              + * This class makes multiple iterators look like one to the caller. When any + * method from the Iterator interface is called, the IteratorChain will delegate + * to a single underlying Iterator. The IteratorChain will invoke the Iterators + * in sequence until all Iterators are exhausted. + *

              + * Under many circumstances, linking Iterators together in this manner is more + * efficient (and convenient) than reading out the contents of each Iterator + * into a List and creating a new Iterator. + *

              + * Calling a method that adds new Iterator after a method in the Iterator + * interface has been called will result in an UnsupportedOperationException. + *

              + * NOTE: As from version 3.0, the IteratorChain may contain no iterators. In + * this case the class will function as an empty iterator. + *

              + * NOTE: As from version 4.0, the IteratorChain stores the iterators in a queue + * and removes any reference to them as soon as they are not used anymore. Thus + * the methods {@code setIterator(Iterator)} and {@code getIterators()} have been + * removed and {@link #size()} will return the number of remaining iterators in + * the queue. + * + * @since 2.1 + * @version $Id: IteratorChain.java 1482514 2013-05-14 18:36:12Z tn $ + */ +public class IteratorChain implements Iterator { + + /** The chain of iterators */ + private final Queue> iteratorChain = new LinkedList>(); + + /** The current iterator */ + private Iterator currentIterator = null; + + /** + * The "last used" Iterator is the Iterator upon which next() or hasNext() + * was most recently called used for the remove() operation only + */ + private Iterator lastUsedIterator = null; + + /** + * ComparatorChain is "locked" after the first time compare(Object,Object) + * is called + */ + private boolean isLocked = false; + + //----------------------------------------------------------------------- + /** + * Construct an IteratorChain with no Iterators. + *

              + * You will normally use {@link #addIterator(Iterator)} to add some + * iterators after using this constructor. + */ + public IteratorChain() { + super(); + } + + /** + * Construct an IteratorChain with a single Iterator. + *

              + * This method takes one iterator. The newly constructed iterator will + * iterate through that iterator. Thus calling this constructor on its own + * will have no effect other than decorating the input iterator. + *

              + * You will normally use {@link #addIterator(Iterator)} to add some more + * iterators after using this constructor. + * + * @param iterator the first child iterator in the IteratorChain, not null + * @throws NullPointerException if the iterator is null + */ + public IteratorChain(final Iterator iterator) { + super(); + addIterator(iterator); + } + + /** + * Constructs a new IteratorChain over the two given iterators. + *

              + * This method takes two iterators. The newly constructed iterator will + * iterate through each one of the input iterators in turn. + * + * @param first the first child iterator in the IteratorChain, not null + * @param second the second child iterator in the IteratorChain, not null + * @throws NullPointerException if either iterator is null + */ + public IteratorChain(final Iterator first, final Iterator second) { + super(); + addIterator(first); + addIterator(second); + } + + /** + * Constructs a new IteratorChain over the array of iterators. + *

              + * This method takes an array of iterators. The newly constructed iterator + * will iterate through each one of the input iterators in turn. + * + * @param iteratorChain the array of iterators, not null + * @throws NullPointerException if iterators array is or contains null + */ + public IteratorChain(final Iterator... iteratorChain) { + super(); + for (final Iterator element : iteratorChain) { + addIterator(element); + } + } + + /** + * Constructs a new IteratorChain over the collection of + * iterators. + *

              + * This method takes a collection of iterators. The newly constructed + * iterator will iterate through each one of the input iterators in turn. + * + * @param iteratorChain the collection of iterators, not null + * @throws NullPointerException if iterators collection is or contains null + * @throws ClassCastException if iterators collection doesn't contain an + * iterator + */ + public IteratorChain(final Collection> iteratorChain) { + super(); + for (final Iterator iterator : iteratorChain) { + addIterator(iterator); + } + } + + //----------------------------------------------------------------------- + /** + * Add an Iterator to the end of the chain + * + * @param iterator Iterator to add + * @throws IllegalStateException if I've already started iterating + * @throws NullPointerException if the iterator is null + */ + public void addIterator(final Iterator iterator) { + checkLocked(); + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + iteratorChain.add(iterator); + } + + /** + * Returns the remaining number of Iterators in the current IteratorChain. + * + * @return Iterator count + */ + public int size() { + return iteratorChain.size(); + } + + /** + * Determine if modifications can still be made to the IteratorChain. + * IteratorChains cannot be modified once they have executed a method from + * the Iterator interface. + * + * @return true if IteratorChain cannot be modified, false if it can + */ + public boolean isLocked() { + return isLocked; + } + + /** + * Checks whether the iterator chain is now locked and in use. + */ + private void checkLocked() { + if (isLocked == true) { + throw new UnsupportedOperationException( + "IteratorChain cannot be changed after the first use of a method from the Iterator interface"); + } + } + + /** + * Lock the chain so no more iterators can be added. This must be called + * from all Iterator interface methods. + */ + private void lockChain() { + if (isLocked == false) { + isLocked = true; + } + } + + /** + * Updates the current iterator field to ensure that the current Iterator is + * not exhausted + */ + protected void updateCurrentIterator() { + if (currentIterator == null) { + if (iteratorChain.isEmpty()) { + currentIterator = EmptyIterator. emptyIterator(); + } else { + currentIterator = iteratorChain.remove(); + } + // set last used iterator here, in case the user calls remove + // before calling hasNext() or next() (although they shouldn't) + lastUsedIterator = currentIterator; + } + + while (currentIterator.hasNext() == false && !iteratorChain.isEmpty()) { + currentIterator = iteratorChain.remove(); + } + } + + //----------------------------------------------------------------------- + /** + * Return true if any Iterator in the IteratorChain has a remaining element. + * + * @return true if elements remain + */ + public boolean hasNext() { + lockChain(); + updateCurrentIterator(); + lastUsedIterator = currentIterator; + + return currentIterator.hasNext(); + } + + /** + * Returns the next Object of the current Iterator + * + * @return Object from the current Iterator + * @throws java.util.NoSuchElementException if all the Iterators are + * exhausted + */ + public E next() { + lockChain(); + updateCurrentIterator(); + lastUsedIterator = currentIterator; + + return currentIterator.next(); + } + + /** + * Removes from the underlying collection the last element returned by the + * Iterator. As with next() and hasNext(), this method calls remove() on the + * underlying Iterator. Therefore, this method may throw an + * UnsupportedOperationException if the underlying Iterator does not support + * this method. + * + * @throws UnsupportedOperationException if the remove operator is not + * supported by the underlying Iterator + * @throws IllegalStateException if the next method has not yet been called, + * or the remove method has already been called after the last call to the + * next method. + */ + public void remove() { + lockChain(); + if (currentIterator == null) { + updateCurrentIterator(); + } + lastUsedIterator.remove(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorEnumeration.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorEnumeration.java new file mode 100644 index 000000000..354991887 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorEnumeration.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Enumeration; +import java.util.Iterator; + +/** + * Adapter to make an {@link Iterator Iterator} instance appear to be an + * {@link Enumeration Enumeration} instance. + * + * @since 1.0 + * @version $Id: IteratorEnumeration.java 1543728 2013-11-20 07:51:32Z ebourg $ + */ +public class IteratorEnumeration implements Enumeration { + + /** The iterator being decorated. */ + private Iterator iterator; + + /** + * Constructs a new IteratorEnumeration that will not function + * until {@link #setIterator(Iterator) setIterator} is invoked. + */ + public IteratorEnumeration() { + } + + /** + * Constructs a new IteratorEnumeration that will use the given + * iterator. + * + * @param iterator the iterator to use + */ + public IteratorEnumeration(final Iterator iterator) { + this.iterator = iterator; + } + + // Iterator interface + //------------------------------------------------------------------------- + + /** + * Returns true if the underlying iterator has more elements. + * + * @return true if the underlying iterator has more elements + */ + public boolean hasMoreElements() { + return iterator.hasNext(); + } + + /** + * Returns the next element from the underlying iterator. + * + * @return the next element from the underlying iterator. + * @throws java.util.NoSuchElementException if the underlying iterator has + * no more elements + */ + public E nextElement() { + return iterator.next(); + } + + // Properties + //------------------------------------------------------------------------- + + /** + * Returns the underlying iterator. + * + * @return the underlying iterator + */ + public Iterator getIterator() { + return iterator; + } + + /** + * Sets the underlying iterator. + * + * @param iterator the new underlying iterator + */ + public void setIterator(final Iterator iterator) { + this.iterator = iterator; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorIterable.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorIterable.java new file mode 100644 index 000000000..4b7bb3cc4 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/IteratorIterable.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law + * or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; + +import org.apache.commons.collections4.ResettableIterator; + +/** + * Adapter to make an {@link Iterator Iterator} instance appear to be an + * {@link Iterable Iterable} instance. The iterable can be constructed in one + * of two variants: single use, multiple use. + * + *

              + * In the single use iterable case, the iterable is only usable for one + * iterative operation over the source iterator. Subsequent iterative + * operations use the same, exhausted source iterator. To create a single use + * iterable, construct a new {@link IteratorIterable} using a {@link Iterator} + * that is NOT a {@link ResettableIterator} iterator: + *

              + *   Iterator iterator = // some non-resettable iterator
              + *   Iterable iterable = new IteratorIterable(iterator);
              + * 
              + *

              + * + *

              + * In the multiple use iterable case, the iterable is usable for any number of + * iterative operations over the source iterator. Of special note, even though + * the iterable supports multiple iterations, it does not support concurrent + * iterations. To implicitly create a multiple use iterable, construct a new + * {@link IteratorIterable} using a {@link ResettableIterator} iterator: + *

              + *   Integer[] array = {Integer.valueOf(1),Integer.valueOf(2),Integer.valueOf(3)};
              + *   Iterator iterator = IteratorUtils.arrayIterator(array); // a resettable iterator
              + *   Iterable iterable = new IteratorIterable(iterator);
              + * 
              + *

              + * + *

              + * A multiple use iterable can also be explicitly constructed using any + * {@link Iterator} and specifying true for the + * multipleUse flag: + *

              + *   Iterator iterator = // some non-resettable iterator
              + *   Iterable iterable = new IteratorIterable(iterator, true);
              + * 
              + *

              + * + * @since 4.0 + * @version $Id: IteratorIterable.java 1684123 2015-06-08 08:53:26Z tn $ + */ +public class IteratorIterable implements Iterable { + + /** + * Factory method to create an {@link Iterator Iterator} from another + * iterator over objects of a different subtype. + */ + private static Iterator createTypesafeIterator(final Iterator iterator) { + return new Iterator() { + public boolean hasNext() { + return iterator.hasNext(); + } + + public E next() { + return iterator.next(); + } + + public void remove() { + iterator.remove(); + } + }; + } + + /** the iterator being adapted into an iterable. */ + private final Iterator iterator; + + /** the iterator parameterized as the {@link #iterator()} return type. */ + private final Iterator typeSafeIterator; + + /** + * Constructs a new IteratorIterable that will use the given + * iterator. + * + * @param iterator the iterator to use. + */ + public IteratorIterable(final Iterator iterator) { + this(iterator, false); + } + + /** + * Constructs a new IteratorIterable that will use the given + * iterator. + * + * @param iterator the iterator to use. + * @param multipleUse true if the new iterable can be used in multiple iterations + */ + public IteratorIterable(final Iterator iterator, final boolean multipleUse) { + super(); + if (multipleUse && !(iterator instanceof ResettableIterator)) { + this.iterator = new ListIteratorWrapper(iterator); + } else { + this.iterator = iterator; + } + this.typeSafeIterator = createTypesafeIterator(this.iterator); + } + + /** + * Gets the iterator wrapped by this iterable. + * + * @return the iterator + */ + public Iterator iterator() { + if (iterator instanceof ResettableIterator) { + ((ResettableIterator)iterator).reset(); + } + return typeSafeIterator; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/LazyIteratorChain.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/LazyIteratorChain.java new file mode 100644 index 000000000..818142cbe --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/LazyIteratorChain.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; + +/** + * An LazyIteratorChain is an Iterator that wraps a number of Iterators in a lazy manner. + *

              + * This class makes multiple iterators look like one to the caller. When any + * method from the Iterator interface is called, the LazyIteratorChain will delegate + * to a single underlying Iterator. The LazyIteratorChain will invoke the Iterators + * in sequence until all Iterators are exhausted. + *

              + * The Iterators are provided by {@link #nextIterator(int)} which has to be overridden by + * sub-classes and allows to lazily create the Iterators as they are accessed: + *

              + * return new LazyIteratorChain<String>() {
              + *     protected Iterator<String> nextIterator(int count) {
              + *         return count == 1 ? Arrays.asList("foo", "bar").iterator() : null;
              + *     }
              + * };
              + * 
              + *

              + * Once the inner Iterator's {@link Iterator#hasNext()} method returns false, + * {@link #nextIterator(int)} will be called to obtain another iterator, and so on + * until {@link #nextIterator(int)} returns null, indicating that the chain is exhausted. + *

              + * NOTE: The LazyIteratorChain may contain no iterators. In this case the class will + * function as an empty iterator. + * + * @since 4.0 + * @version $Id: LazyIteratorChain.java 1482073 2013-05-13 20:09:40Z tn $ + */ +public abstract class LazyIteratorChain implements Iterator { + + /** The number of times {@link #nextIterator()} was already called. */ + private int callCounter = 0; + + /** Indicates that the Iterator chain has been exhausted. */ + private boolean chainExhausted = false; + + /** The current iterator. */ + private Iterator currentIterator = null; + + /** + * The "last used" Iterator is the Iterator upon which next() or hasNext() + * was most recently called used for the remove() operation only. + */ + private Iterator lastUsedIterator = null; + + //----------------------------------------------------------------------- + + /** + * Gets the next iterator after the previous one has been exhausted. + *

              + * This method MUST return null when there are no more iterators. + * + * @param count the number of time this method has been called (starts with 1) + * @return the next iterator, or null if there are no more. + */ + protected abstract Iterator nextIterator(int count); + + /** + * Updates the current iterator field to ensure that the current Iterator + * is not exhausted. + */ + private void updateCurrentIterator() { + if (callCounter == 0) { + currentIterator = nextIterator(++callCounter); + if (currentIterator == null) { + currentIterator = EmptyIterator.emptyIterator(); + chainExhausted = true; + } + // set last used iterator here, in case the user calls remove + // before calling hasNext() or next() (although they shouldn't) + lastUsedIterator = currentIterator; + } + + while (currentIterator.hasNext() == false && !chainExhausted) { + final Iterator nextIterator = nextIterator(++callCounter); + if (nextIterator != null) { + currentIterator = nextIterator; + } else { + chainExhausted = true; + } + } + } + + //----------------------------------------------------------------------- + + /** + * Return true if any Iterator in the chain has a remaining element. + * + * @return true if elements remain + */ + public boolean hasNext() { + updateCurrentIterator(); + lastUsedIterator = currentIterator; + + return currentIterator.hasNext(); + } + + /** + * Returns the next element of the current Iterator + * + * @return element from the current Iterator + * @throws java.util.NoSuchElementException if all the Iterators are exhausted + */ + public E next() { + updateCurrentIterator(); + lastUsedIterator = currentIterator; + + return currentIterator.next(); + } + + /** + * Removes from the underlying collection the last element returned by the Iterator. + *

              + * As with next() and hasNext(), this method calls remove() on the underlying Iterator. + * Therefore, this method may throw an UnsupportedOperationException if the underlying + * Iterator does not support this method. + * + * @throws UnsupportedOperationException if the remove operator is not + * supported by the underlying Iterator + * @throws IllegalStateException if the next method has not yet been called, + * or the remove method has already been called after the last call to the next method. + */ + public void remove() { + if (currentIterator == null) { + updateCurrentIterator(); + } + lastUsedIterator.remove(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ListIteratorWrapper.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ListIteratorWrapper.java new file mode 100644 index 000000000..1341051f2 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ListIteratorWrapper.java @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.ResettableListIterator; + +/** + * Converts an {@link Iterator} into a {@link ResettableListIterator}. + * For plain Iterators this is accomplished by caching the returned + * elements. This class can also be used to simply add + * {@link org.apache.commons.collections4.ResettableIterator ResettableIterator} + * functionality to a given {@link ListIterator}. + *

              + * The ListIterator interface has additional useful methods + * for navigation - previous() and the index methods. + * This class allows a regular Iterator to behave as a + * ListIterator. It achieves this by building a list internally + * of as the underlying iterator is traversed. + *

              + * The optional operations of ListIterator are not supported for plain Iterators. + *

              + * This class implements ResettableListIterator from Commons Collections 3.2. + * + * @since 2.1 + * @version $Id: ListIteratorWrapper.java 1477802 2013-04-30 20:01:28Z tn $ + */ +public class ListIteratorWrapper implements ResettableListIterator { + + /** Message used when set or add are called. */ + private static final String UNSUPPORTED_OPERATION_MESSAGE = + "ListIteratorWrapper does not support optional operations of ListIterator."; + + /** Message used when set or add are called. */ + private static final String CANNOT_REMOVE_MESSAGE = "Cannot remove element at index {0}."; + + /** The underlying iterator being decorated. */ + private final Iterator iterator; + /** The list being used to cache the iterator. */ + private final List list = new ArrayList(); + + /** The current index of this iterator. */ + private int currentIndex = 0; + /** The current index of the wrapped iterator. */ + private int wrappedIteratorIndex = 0; + /** recall whether the wrapped iterator's "cursor" is in such a state as to allow remove() to be called */ + private boolean removeState; + + // Constructor + //------------------------------------------------------------------------- + /** + * Constructs a new ListIteratorWrapper that will wrap + * the given iterator. + * + * @param iterator the iterator to wrap + * @throws NullPointerException if the iterator is null + */ + public ListIteratorWrapper(final Iterator iterator) { + super(); + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + this.iterator = iterator; + } + + // ListIterator interface + //------------------------------------------------------------------------- + /** + * Throws {@link UnsupportedOperationException} + * unless the underlying Iterator is a ListIterator. + * + * @param obj the object to add + * @throws UnsupportedOperationException if the underlying iterator is not of + * type {@link ListIterator} + */ + public void add(final E obj) throws UnsupportedOperationException { + if (iterator instanceof ListIterator) { + @SuppressWarnings("unchecked") + final ListIterator li = (ListIterator) iterator; + li.add(obj); + return; + } + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE); + } + + /** + * Returns true if there are more elements in the iterator. + * + * @return true if there are more elements + */ + public boolean hasNext() { + if (currentIndex == wrappedIteratorIndex || iterator instanceof ListIterator) { + return iterator.hasNext(); + } + return true; + } + + /** + * Returns true if there are previous elements in the iterator. + * + * @return true if there are previous elements + */ + public boolean hasPrevious() { + if (iterator instanceof ListIterator) { + final ListIterator li = (ListIterator) iterator; + return li.hasPrevious(); + } + return currentIndex > 0; + } + + /** + * Returns the next element from the iterator. + * + * @return the next element from the iterator + * @throws NoSuchElementException if there are no more elements + */ + public E next() throws NoSuchElementException { + if (iterator instanceof ListIterator) { + return iterator.next(); + } + + if (currentIndex < wrappedIteratorIndex) { + ++currentIndex; + return list.get(currentIndex - 1); + } + + final E retval = iterator.next(); + list.add(retval); + ++currentIndex; + ++wrappedIteratorIndex; + removeState = true; + return retval; + } + + /** + * Returns the index of the next element. + * + * @return the index of the next element + */ + public int nextIndex() { + if (iterator instanceof ListIterator) { + final ListIterator li = (ListIterator) iterator; + return li.nextIndex(); + } + return currentIndex; + } + + /** + * Returns the the previous element. + * + * @return the previous element + * @throws NoSuchElementException if there are no previous elements + */ + public E previous() throws NoSuchElementException { + if (iterator instanceof ListIterator) { + @SuppressWarnings("unchecked") + final ListIterator li = (ListIterator) iterator; + return li.previous(); + } + + if (currentIndex == 0) { + throw new NoSuchElementException(); + } + removeState = wrappedIteratorIndex == currentIndex; + return list.get(--currentIndex); + } + + /** + * Returns the index of the previous element. + * + * @return the index of the previous element + */ + public int previousIndex() { + if (iterator instanceof ListIterator) { + final ListIterator li = (ListIterator) iterator; + return li.previousIndex(); + } + return currentIndex - 1; + } + + /** + * Throws {@link UnsupportedOperationException} if {@link #previous()} has ever been called. + * + * @throws UnsupportedOperationException always + */ + public void remove() throws UnsupportedOperationException { + if (iterator instanceof ListIterator) { + iterator.remove(); + return; + } + int removeIndex = currentIndex; + if (currentIndex == wrappedIteratorIndex) { + --removeIndex; + } + if (!removeState || wrappedIteratorIndex - currentIndex > 1) { + throw new IllegalStateException(MessageFormat.format(CANNOT_REMOVE_MESSAGE, Integer.valueOf(removeIndex))); + } + iterator.remove(); + list.remove(removeIndex); + currentIndex = removeIndex; + wrappedIteratorIndex--; + removeState = false; + } + + /** + * Throws {@link UnsupportedOperationException} + * unless the underlying Iterator is a ListIterator. + * + * @param obj the object to set + * @throws UnsupportedOperationException if the underlying iterator is not of + * type {@link ListIterator} + */ + public void set(final E obj) throws UnsupportedOperationException { + if (iterator instanceof ListIterator) { + @SuppressWarnings("unchecked") + final ListIterator li = (ListIterator) iterator; + li.set(obj); + return; + } + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE); + } + + // ResettableIterator interface + //------------------------------------------------------------------------- + /** + * Resets this iterator back to the position at which the iterator + * was created. + * + * @since 3.2 + */ + public void reset() { + if (iterator instanceof ListIterator) { + final ListIterator li = (ListIterator) iterator; + while (li.previousIndex() >= 0) { + li.previous(); + } + return; + } + currentIndex = 0; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/LoopingIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/LoopingIterator.java new file mode 100644 index 000000000..0a5ca4ecc --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/LoopingIterator.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.ResettableIterator; + +/** + * An Iterator that restarts when it reaches the end. + *

              + * The iterator will loop continuously around the provided elements, unless + * there are no elements in the collection to begin with, or all the elements + * have been {@link #remove removed}. + *

              + * Concurrent modifications are not directly supported, and for most collection + * implementations will throw a ConcurrentModificationException. + * + * @since 3.0 + * @version $Id: LoopingIterator.java 1477802 2013-04-30 20:01:28Z tn $ + */ +public class LoopingIterator implements ResettableIterator { + + /** The collection to base the iterator on */ + private final Collection collection; + /** The current iterator */ + private Iterator iterator; + + /** + * Constructor that wraps a collection. + *

              + * There is no way to reset an Iterator instance without recreating it from + * the original source, so the Collection must be passed in. + * + * @param coll the collection to wrap + * @throws NullPointerException if the collection is null + */ + public LoopingIterator(final Collection coll) { + if (coll == null) { + throw new NullPointerException("The collection must not be null"); + } + collection = coll; + reset(); + } + + /** + * Has the iterator any more elements. + *

              + * Returns false only if the collection originally had zero elements, or + * all the elements have been {@link #remove removed}. + * + * @return true if there are more elements + */ + public boolean hasNext() { + return collection.size() > 0; + } + + /** + * Returns the next object in the collection. + *

              + * If at the end of the collection, return the first element. + * + * @return the next object + * @throws NoSuchElementException if there are no elements + * at all. Use {@link #hasNext} to avoid this error. + */ + public E next() { + if (collection.size() == 0) { + throw new NoSuchElementException("There are no elements for this iterator to loop on"); + } + if (iterator.hasNext() == false) { + reset(); + } + return iterator.next(); + } + + /** + * Removes the previously retrieved item from the underlying collection. + *

              + * This feature is only supported if the underlying collection's + * {@link Collection#iterator iterator} method returns an implementation + * that supports it. + *

              + * This method can only be called after at least one {@link #next} method call. + * After a removal, the remove method may not be called again until another + * next has been performed. If the {@link #reset} is called, then remove may + * not be called until {@link #next} is called again. + */ + public void remove() { + iterator.remove(); + } + + /** + * Resets the iterator back to the start of the collection. + */ + public void reset() { + iterator = collection.iterator(); + } + + /** + * Gets the size of the collection underlying the iterator. + * + * @return the current collection size + */ + public int size() { + return collection.size(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/LoopingListIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/LoopingListIterator.java new file mode 100644 index 000000000..cdefbd644 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/LoopingListIterator.java @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.ResettableListIterator; + +/** + * A ListIterator that restarts when it reaches the end or when it + * reaches the beginning. + *

              + * The iterator will loop continuously around the provided list, + * unless there are no elements in the collection to begin with, or + * all of the elements have been {@link #remove removed}. + *

              + * Concurrent modifications are not directly supported, and for most + * collection implementations will throw a + * ConcurrentModificationException. + * + * @since 3.2 + * @version $Id: LoopingListIterator.java 1494333 2013-06-18 21:59:21Z sebb $ + */ +public class LoopingListIterator implements ResettableListIterator { + + /** The list to base the iterator on */ + private final List list; + /** The current list iterator */ + private ListIterator iterator; + + /** + * Constructor that wraps a list. + *

              + * There is no way to reset a ListIterator instance without + * recreating it from the original source, so the List must be + * passed in and a reference to it held. + * + * @param list the list to wrap + * @throws NullPointerException if the list it null + */ + public LoopingListIterator(final List list) { + if (list == null) { + throw new NullPointerException("The list must not be null"); + } + this.list = list; + _reset(); + } + + /** + * Returns whether this iterator has any more elements. + *

              + * Returns false only if the list originally had zero elements, or + * all elements have been {@link #remove removed}. + * + * @return true if there are more elements + */ + public boolean hasNext() { + return !list.isEmpty(); + } + + /** + * Returns the next object in the list. + *

              + * If at the end of the list, returns the first element. + * + * @return the object after the last element returned + * @throws NoSuchElementException if there are no elements in the list + */ + public E next() { + if (list.isEmpty()) { + throw new NoSuchElementException( + "There are no elements for this iterator to loop on"); + } + if (iterator.hasNext() == false) { + reset(); + } + return iterator.next(); + } + + /** + * Returns the index of the element that would be returned by a + * subsequent call to {@link #next}. + *

              + * As would be expected, if the iterator is at the physical end of + * the underlying list, 0 is returned, signifying the beginning of + * the list. + * + * @return the index of the element that would be returned if next() were called + * @throws NoSuchElementException if there are no elements in the list + */ + public int nextIndex() { + if (list.isEmpty()) { + throw new NoSuchElementException( + "There are no elements for this iterator to loop on"); + } + if (iterator.hasNext() == false) { + return 0; + } + return iterator.nextIndex(); + } + + /** + * Returns whether this iterator has any more previous elements. + *

              + * Returns false only if the list originally had zero elements, or + * all elements have been {@link #remove removed}. + * + * @return true if there are more elements + */ + public boolean hasPrevious() { + return !list.isEmpty(); + } + + /** + * Returns the previous object in the list. + *

              + * If at the beginning of the list, return the last element. Note + * that in this case, traversal to find that element takes linear time. + * + * @return the object before the last element returned + * @throws NoSuchElementException if there are no elements in the list + */ + public E previous() { + if (list.isEmpty()) { + throw new NoSuchElementException( + "There are no elements for this iterator to loop on"); + } + if (iterator.hasPrevious() == false) { + E result = null; + while (iterator.hasNext()) { + result = iterator.next(); + } + iterator.previous(); + return result; + } + return iterator.previous(); + } + + /** + * Returns the index of the element that would be returned by a + * subsequent call to {@link #previous}. + *

              + * As would be expected, if at the iterator is at the physical + * beginning of the underlying list, the list's size minus one is + * returned, signifying the end of the list. + * + * @return the index of the element that would be returned if previous() were called + * @throws NoSuchElementException if there are no elements in the list + */ + public int previousIndex() { + if (list.isEmpty()) { + throw new NoSuchElementException( + "There are no elements for this iterator to loop on"); + } + if (iterator.hasPrevious() == false) { + return list.size() - 1; + } + return iterator.previousIndex(); + } + + /** + * Removes the previously retrieved item from the underlying list. + *

              + * This feature is only supported if the underlying list's + * {@link List#iterator iterator} method returns an implementation + * that supports it. + *

              + * This method can only be called after at least one {@link #next} + * or {@link #previous} method call. After a removal, the remove + * method may not be called again until another {@link #next} or + * {@link #previous} has been performed. If the {@link #reset} is + * called, then remove may not be called until {@link #next} or + * {@link #previous} is called again. + * + * @throws UnsupportedOperationException if the remove method is + * not supported by the iterator implementation of the underlying + * list + */ + public void remove() { + iterator.remove(); + } + + /** + * Inserts the specified element into the underlying list. + *

              + * The element is inserted before the next element that would be + * returned by {@link #next}, if any, and after the next element + * that would be returned by {@link #previous}, if any. + *

              + * This feature is only supported if the underlying list's + * {@link List#listIterator} method returns an implementation + * that supports it. + * + * @param obj the element to insert + * @throws UnsupportedOperationException if the add method is not + * supported by the iterator implementation of the underlying list + */ + public void add(final E obj) { + iterator.add(obj); + } + + /** + * Replaces the last element that was returned by {@link #next} or + * {@link #previous}. + *

              + * This feature is only supported if the underlying list's + * {@link List#listIterator} method returns an implementation + * that supports it. + * + * @param obj the element with which to replace the last element returned + * @throws UnsupportedOperationException if the set method is not + * supported by the iterator implementation of the underlying list + */ + public void set(final E obj) { + iterator.set(obj); + } + + /** + * Resets the iterator back to the start of the list. + */ + public void reset() { + _reset(); + } + + private void _reset() { + iterator = list.listIterator(); + } + + /** + * Gets the size of the list underlying the iterator. + * + * @return the current list size + */ + public int size() { + return list.size(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/NodeListIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/NodeListIterator.java new file mode 100644 index 000000000..a5c1575e1 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/NodeListIterator.java @@ -0,0 +1,88 @@ +/* + * Copyright 2013 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * An {@link Iterator} over a {@link NodeList}. + *

              + * This iterator does not support {@link #remove()} as a {@link NodeList} does not support + * removal of items. + * + * @since 4.0 + * @version $Id: NodeListIterator.java 1686855 2015-06-22 13:00:27Z tn $ + * @see NodeList + */ +public class NodeListIterator implements Iterator { + + /** the original NodeList instance */ + private final NodeList nodeList; + /** The current iterator index */ + private int index = 0; + + /** + * Convenience constructor, which creates a new NodeListIterator from + * the specified node's childNodes. + * + * @param node Node, who's child nodes are wrapped by this class. Must not be null + * @throws NullPointerException if node is null + */ + public NodeListIterator(final Node node) { + if (node == null) { + throw new NullPointerException("Node must not be null."); + } + this.nodeList = node.getChildNodes(); + } + + /** + * Constructor, that creates a new NodeListIterator from the specified + * org.w3c.NodeList + * + * @param nodeList node list, which is wrapped by this class. Must not be null + * @throws NullPointerException if nodeList is null + */ + public NodeListIterator(final NodeList nodeList) { + if (nodeList == null) { + throw new NullPointerException("NodeList must not be null."); + } + this.nodeList = nodeList; + } + + public boolean hasNext() { + return nodeList == null ? false : index < nodeList.getLength(); + } + + public Node next() { + if (nodeList != null && index < nodeList.getLength()) { + return nodeList.item(index++); + } + throw new NoSuchElementException("underlying nodeList has no more elements"); + } + + /** + * Throws {@link UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + */ + public void remove() { + throw new UnsupportedOperationException("remove() method not supported for a NodeListIterator."); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectArrayIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectArrayIterator.java new file mode 100644 index 000000000..4b853d164 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectArrayIterator.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.ResettableIterator; + +/** + * An {@link Iterator} over an array of objects. + *

              + * This iterator does not support {@link #remove}, as the object array cannot be + * structurally modified. + *

              + * The iterator implements a {@link #reset} method, allowing the reset of the iterator + * back to the start if required. + * + * @since 3.0 + * @version $Id: ObjectArrayIterator.java 1543928 2013-11-20 20:15:35Z tn $ + */ +public class ObjectArrayIterator implements ResettableIterator { + + /** The array */ + final E[] array; + /** The start index to loop from */ + final int startIndex; + /** The end index to loop to */ + final int endIndex; + /** The current iterator index */ + int index = 0; + + //------------------------------------------------------------------------- + /** + * Constructs an ObjectArrayIterator that will iterate over the values in the + * specified array. + * + * @param array the array to iterate over + * @throws NullPointerException if array is null + */ + public ObjectArrayIterator(final E... array) { + this(array, 0, array.length); + } + + /** + * Constructs an ObjectArrayIterator that will iterate over the values in the + * specified array from a specific start index. + * + * @param array the array to iterate over + * @param start the index to start iterating at + * @throws NullPointerException if array is null + * @throws IndexOutOfBoundsException if the start index is out of bounds + */ + public ObjectArrayIterator(final E array[], final int start) { + this(array, start, array.length); + } + + /** + * Construct an ObjectArrayIterator that will iterate over a range of values + * in the specified array. + * + * @param array the array to iterate over + * @param start the index to start iterating at + * @param end the index (exclusive) to finish iterating at + * @throws IndexOutOfBoundsException if the start or end index is out of bounds + * @throws IllegalArgumentException if end index is before the start + * @throws NullPointerException if array is null + */ + public ObjectArrayIterator(final E array[], final int start, final int end) { + super(); + if (start < 0) { + throw new ArrayIndexOutOfBoundsException("Start index must not be less than zero"); + } + if (end > array.length) { + throw new ArrayIndexOutOfBoundsException("End index must not be greater than the array length"); + } + if (start > array.length) { + throw new ArrayIndexOutOfBoundsException("Start index must not be greater than the array length"); + } + if (end < start) { + throw new IllegalArgumentException("End index must not be less than start index"); + } + this.array = array; + this.startIndex = start; + this.endIndex = end; + this.index = start; + } + + // Iterator interface + //------------------------------------------------------------------------- + + /** + * Returns true if there are more elements to return from the array. + * + * @return true if there is a next element to return + */ + public boolean hasNext() { + return this.index < this.endIndex; + } + + /** + * Returns the next element in the array. + * + * @return the next element in the array + * @throws NoSuchElementException if all the elements in the array + * have already been returned + */ + public E next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + return this.array[this.index++]; + } + + /** + * Throws {@link UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + */ + public void remove() { + throw new UnsupportedOperationException("remove() method is not supported for an ObjectArrayIterator"); + } + + // Properties + //------------------------------------------------------------------------- + + /** + * Gets the array that this iterator is iterating over. + * + * @return the array this iterator iterates over + */ + public E[] getArray() { + return this.array; + } + + /** + * Gets the start index to loop from. + * + * @return the start index + */ + public int getStartIndex() { + return this.startIndex; + } + + /** + * Gets the end index to loop to. + * + * @return the end index + */ + public int getEndIndex() { + return this.endIndex; + } + + /** + * Resets the iterator back to the start index. + */ + public void reset() { + this.index = this.startIndex; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectArrayListIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectArrayListIterator.java new file mode 100644 index 000000000..e681300dd --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectArrayListIterator.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.ResettableListIterator; + +/** + * Implements a {@link ListIterator} over an array of objects. + *

              + * This iterator does not support {@link #add} or {@link #remove}, as the object array + * cannot be structurally modified. The {@link #set} method is supported however. + *

              + * The iterator implements a {@link #reset} method, allowing the reset of the iterator + * back to the start if required. + * + * @see org.apache.commons.collections4.iterators.ObjectArrayIterator + * @see java.util.Iterator + * @see java.util.ListIterator + * + * @since 3.0 + * @version $Id: ObjectArrayListIterator.java 1543928 2013-11-20 20:15:35Z tn $ + */ +public class ObjectArrayListIterator extends ObjectArrayIterator + implements ResettableListIterator { + + /** + * Holds the index of the last item returned by a call to next() + * or previous(). This is set to -1 if neither method + * has yet been invoked. lastItemIndex is used to to implement the + * {@link #set} method. + */ + private int lastItemIndex = -1; + + //------------------------------------------------------------------------- + /** + * Constructs an ObjectArrayListIterator that will iterate over the values in the + * specified array. + * + * @param array the array to iterate over + * @throws NullPointerException if array is null + */ + public ObjectArrayListIterator(final E... array) { + super(array); + } + + /** + * Constructs an ObjectArrayListIterator that will iterate over the values in the + * specified array from a specific start index. + * + * @param array the array to iterate over + * @param start the index to start iterating at + * @throws NullPointerException if array is null + * @throws IndexOutOfBoundsException if the start index is out of bounds + */ + public ObjectArrayListIterator(final E[] array, final int start) { + super(array, start); + } + + /** + * Construct an ObjectArrayListIterator that will iterate over a range of values + * in the specified array. + * + * @param array the array to iterate over + * @param start the index to start iterating at + * @param end the index (exclusive) to finish iterating at + * @throws IndexOutOfBoundsException if the start or end index is out of bounds + * @throws IllegalArgumentException if end index is before the start + * @throws NullPointerException if array is null + */ + public ObjectArrayListIterator(final E[] array, final int start, final int end) { + super(array, start, end); + } + + // ListIterator interface + //------------------------------------------------------------------------- + + /** + * Returns true if there are previous elements to return from the array. + * + * @return true if there is a previous element to return + */ + public boolean hasPrevious() { + return this.index > getStartIndex(); + } + + /** + * Gets the previous element from the array. + * + * @return the previous element + * @throws NoSuchElementException if there is no previous element + */ + public E previous() { + if (hasPrevious() == false) { + throw new NoSuchElementException(); + } + this.lastItemIndex = --this.index; + return this.array[this.index]; + } + + /** + * Gets the next element from the array. + * + * @return the next element + * @throws NoSuchElementException if there is no next element + */ + @Override + public E next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + this.lastItemIndex = this.index; + return this.array[this.index++]; + } + + /** + * Gets the next index to be retrieved. + * + * @return the index of the item to be retrieved next + */ + public int nextIndex() { + return this.index - getStartIndex(); + } + + /** + * Gets the index of the item to be retrieved if {@link #previous()} is called. + * + * @return the index of the item to be retrieved next + */ + public int previousIndex() { + return this.index - getStartIndex() - 1; + } + + /** + * This iterator does not support modification of its backing array's size, and so will + * always throw an {@link UnsupportedOperationException} when this method is invoked. + * + * @param obj the object to add + * @throws UnsupportedOperationException always thrown. + */ + public void add(final E obj) { + throw new UnsupportedOperationException("add() method is not supported"); + } + + /** + * Sets the element under the cursor. + *

              + * This method sets the element that was returned by the last call + * to {@link #next()} of {@link #previous()}. + * + * Note: {@link ListIterator} implementations that support add() + * and remove() only allow set() to be called once per call + * to next() or previous (see the {@link ListIterator} + * javadoc for more details). Since this implementation does not support + * add() or remove(), set() may be + * called as often as desired. + * + * @param obj the object to set into the array + * @throws IllegalStateException if next() has not yet been called. + * @throws ClassCastException if the object type is unsuitable for the array + */ + public void set(final E obj) { + if (this.lastItemIndex == -1) { + throw new IllegalStateException("must call next() or previous() before a call to set()"); + } + + this.array[this.lastItemIndex] = obj; + } + + /** + * Resets the iterator back to the start index. + */ + @Override + public void reset() { + super.reset(); + this.lastItemIndex = -1; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectGraphIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectGraphIterator.java new file mode 100644 index 000000000..d8b950c29 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ObjectGraphIterator.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.Transformer; + +/** + * An Iterator that can traverse multiple iterators down an object graph. + *

              + * This iterator can extract multiple objects from a complex tree-like object graph. + * The iteration starts from a single root object. + * It uses a Transformer to extract the iterators and elements. + * Its main benefit is that no intermediate List is created. + *

              + * For example, consider an object graph: + *

              + *                 |- Branch -- Leaf
              + *                 |         \- Leaf
              + *         |- Tree |         /- Leaf
              + *         |       |- Branch -- Leaf
              + *  Forest |                 \- Leaf
              + *         |       |- Branch -- Leaf
              + *         |       |         \- Leaf
              + *         |- Tree |         /- Leaf
              + *                 |- Branch -- Leaf
              + *                 |- Branch -- Leaf
              + * The following Transformer, used in this class, will extract all + * the Leaf objects without creating a combined intermediate list: + *
              + * public Object transform(Object input) {
              + *   if (input instanceof Forest) {
              + *     return ((Forest) input).treeIterator();
              + *   }
              + *   if (input instanceof Tree) {
              + *     return ((Tree) input).branchIterator();
              + *   }
              + *   if (input instanceof Branch) {
              + *     return ((Branch) input).leafIterator();
              + *   }
              + *   if (input instanceof Leaf) {
              + *     return input;
              + *   }
              + *   throw new ClassCastException();
              + * }
              + *

              + * Internally, iteration starts from the root object. When next is called, + * the transformer is called to examine the object. The transformer will return + * either an iterator or an object. If the object is an Iterator, the next element + * from that iterator is obtained and the process repeats. If the element is an object + * it is returned. + *

              + * Under many circumstances, linking Iterators together in this manner is + * more efficient (and convenient) than using nested for loops to extract a list. + * + * @since 3.1 + * @version $Id: ObjectGraphIterator.java 1681434 2015-05-24 10:49:58Z tn $ + */ +public class ObjectGraphIterator implements Iterator { + + /** The stack of iterators */ + private final Deque> stack = new ArrayDeque>(8); + /** The root object in the tree */ + private E root; + /** The transformer to use */ + private final Transformer transformer; + + /** Whether there is another element in the iteration */ + private boolean hasNext = false; + /** The current iterator */ + private Iterator currentIterator; + /** The current value */ + private E currentValue; + /** The last used iterator, needed for remove() */ + private Iterator lastUsedIterator; + + //----------------------------------------------------------------------- + /** + * Constructs an ObjectGraphIterator using a root object and transformer. + *

              + * The root object can be an iterator, in which case it will be immediately + * looped around. + * + * @param root the root object, null will result in an empty iterator + * @param transformer the transformer to use, null will use a no effect transformer + */ + @SuppressWarnings("unchecked") + public ObjectGraphIterator(final E root, final Transformer transformer) { + super(); + if (root instanceof Iterator) { + this.currentIterator = (Iterator) root; + } else { + this.root = root; + } + this.transformer = transformer; + } + + /** + * Constructs a ObjectGraphIterator that will handle an iterator of iterators. + *

              + * This constructor exists for convenience to emphasise that this class can + * be used to iterate over nested iterators. That is to say that the iterator + * passed in here contains other iterators, which may in turn contain further + * iterators. + * + * @param rootIterator the root iterator, null will result in an empty iterator + */ + public ObjectGraphIterator(final Iterator rootIterator) { + super(); + this.currentIterator = rootIterator; + this.transformer = null; + } + + //----------------------------------------------------------------------- + /** + * Loops around the iterators to find the next value to return. + */ + protected void updateCurrentIterator() { + if (hasNext) { + return; + } + if (currentIterator == null) { + if (root == null) { // NOPMD + // do nothing, hasNext will be false + } else { + if (transformer == null) { + findNext(root); + } else { + findNext(transformer.transform(root)); + } + root = null; + } + } else { + findNextByIterator(currentIterator); + } + } + + /** + * Finds the next object in the iteration given any start object. + * + * @param value the value to start from + */ + @SuppressWarnings("unchecked") + protected void findNext(final E value) { + if (value instanceof Iterator) { + // need to examine this iterator + findNextByIterator((Iterator) value); + } else { + // next value found + currentValue = value; + hasNext = true; + } + } + + /** + * Finds the next object in the iteration given an iterator. + * + * @param iterator the iterator to start from + */ + protected void findNextByIterator(final Iterator iterator) { + if (iterator != currentIterator) { + // recurse a level + if (currentIterator != null) { + stack.push(currentIterator); + } + currentIterator = iterator; + } + + while (currentIterator.hasNext() && hasNext == false) { + E next = currentIterator.next(); + if (transformer != null) { + next = transformer.transform(next); + } + findNext(next); + } + // if we havn't found the next value and iterators are not yet exhausted + if (!hasNext && !stack.isEmpty()) { + // current iterator exhausted, go up a level + currentIterator = stack.pop(); + findNextByIterator(currentIterator); + } + } + + //----------------------------------------------------------------------- + /** + * Checks whether there are any more elements in the iteration to obtain. + * + * @return true if elements remain in the iteration + */ + public boolean hasNext() { + updateCurrentIterator(); + return hasNext; + } + + /** + * Gets the next element of the iteration. + * + * @return the next element from the iteration + * @throws NoSuchElementException if all the Iterators are exhausted + */ + public E next() { + updateCurrentIterator(); + if (hasNext == false) { + throw new NoSuchElementException("No more elements in the iteration"); + } + lastUsedIterator = currentIterator; + final E result = currentValue; + currentValue = null; + hasNext = false; + return result; + } + + /** + * Removes from the underlying collection the last element returned. + *

              + * This method calls remove() on the underlying Iterator and it may + * throw an UnsupportedOperationException if the underlying Iterator + * does not support this method. + * + * @throws UnsupportedOperationException + * if the remove operator is not supported by the underlying Iterator + * @throws IllegalStateException + * if the next method has not yet been called, or the remove method has + * already been called after the last call to the next method. + */ + public void remove() { + if (lastUsedIterator == null) { + throw new IllegalStateException("Iterator remove() cannot be called at this time"); + } + lastUsedIterator.remove(); + lastUsedIterator = null; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/PeekingIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/PeekingIterator.java new file mode 100644 index 000000000..080398fd4 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/PeekingIterator.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Decorates an iterator to support one-element lookahead while iterating. + *

              + * The decorator supports the removal operation, but an {@link IllegalStateException} + * will be thrown if {@link #remove()} is called directly after a call to + * {@link #peek()} or {@link #element()}. + * + * @since 4.0 + * @version $Id: PeekingIterator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PeekingIterator implements Iterator { + + /** The iterator being decorated. */ + private final Iterator iterator; + + /** Indicates that the decorated iterator is exhausted. */ + private boolean exhausted = false; + + /** Indicates if the lookahead slot is filled. */ + private boolean slotFilled = false; + + /** The current slot for lookahead. */ + private E slot; + + //----------------------------------------------------------------------- + /** + * Decorates the specified iterator to support one-element lookahead. + *

              + * If the iterator is already a {@link PeekingIterator} it is returned directly. + * + * @param the element type + * @param iterator the iterator to decorate + * @return a new peeking iterator + * @throws NullPointerException if the iterator is null + */ + public static PeekingIterator peekingIterator(final Iterator iterator) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + if (iterator instanceof PeekingIterator) { + @SuppressWarnings("unchecked") // safe cast + final PeekingIterator it = (PeekingIterator) iterator; + return it; + } + return new PeekingIterator(iterator); + } + + //----------------------------------------------------------------------- + + /** + * Constructor. + * + * @param iterator the iterator to decorate + */ + public PeekingIterator(final Iterator iterator) { + this.iterator = iterator; + } + + private void fill() { + if (exhausted || slotFilled) { + return; + } + if (iterator.hasNext()) { + slot = iterator.next(); + slotFilled = true; + } else { + exhausted = true; + slot = null; + slotFilled = false; + } + } + + //----------------------------------------------------------------------- + public boolean hasNext() { + if (exhausted) { + return false; + } + return slotFilled ? true : iterator.hasNext(); + } + + /** + * Returns the next element in iteration without advancing the underlying iterator. + * If the iterator is already exhausted, null will be returned. + *

              + * Note: this method does not throw a {@link NoSuchElementException} if the iterator + * is already exhausted. If you want such a behavior, use {@link #element()} instead. + *

              + * The rationale behind this is to follow the {@link java.util.Queue} interface + * which uses the same terminology. + * + * @return the next element from the iterator + */ + public E peek() { + fill(); + return exhausted ? null : slot; + } + + /** + * Returns the next element in iteration without advancing the underlying iterator. + * If the iterator is already exhausted, null will be returned. + * + * @return the next element from the iterator + * @throws NoSuchElementException if the iterator is already exhausted according to {@link #hasNext()} + */ + public E element() { + fill(); + if (exhausted) { + throw new NoSuchElementException(); + } + return slot; + } + + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + final E x = slotFilled ? slot : iterator.next(); + // reset the lookahead slot + slot = null; + slotFilled = false; + return x; + } + + /** + * {@inheritDoc} + * + * @throws IllegalStateException if {@link #peek()} or {@link #element()} has been called + * prior to the call to {@link #remove()} + */ + public void remove() { + if (slotFilled) { + throw new IllegalStateException("peek() or element() called before remove()"); + } + iterator.remove(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/PermutationIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/PermutationIterator.java new file mode 100644 index 000000000..8193eadab --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/PermutationIterator.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * This iterator creates permutations of an input collection, using the + * Steinhaus-Johnson-Trotter algorithm (also called plain changes). + *

              + * The iterator will return exactly n! permutations of the input collection. + * The {@code remove()} operation is not supported, and will throw an + * {@code UnsupportedOperationException}. + *

              + * NOTE: in case an empty collection is provided, the iterator will + * return exactly one empty list as result, as 0! = 1. + * + * @param the type of the objects being permuted + * + * @version $Id: PermutationIterator.java 1533984 2013-10-20 21:12:51Z tn $ + * @since 4.0 + */ +public class PermutationIterator implements Iterator> { + + /** + * Permutation is done on theses keys to handle equal objects. + */ + private int[] keys; + + /** + * Mapping between keys and objects. + */ + private Map objectMap; + + /** + * Direction table used in the algorithm: + *

                + *
              • false is left
              • + *
              • true is right
              • + *
              + */ + private boolean[] direction; + + /** + * Next permutation to return. When a permutation is requested + * this instance is provided and the next one is computed. + */ + private List nextPermutation; + + /** + * Standard constructor for this class. + * @param coll the collection to generate permutations for + * @throws NullPointerException if coll is null + */ + public PermutationIterator(final Collection coll) { + if (coll == null) { + throw new NullPointerException("The collection must not be null"); + } + + keys = new int[coll.size()]; + direction = new boolean[coll.size()]; + Arrays.fill(direction, false); + int value = 1; + objectMap = new HashMap(); + for (E e : coll) { + objectMap.put(Integer.valueOf(value), e); + keys[value - 1] = value; + value++; + } + nextPermutation = new ArrayList(coll); + } + + /** + * Indicates if there are more permutation available. + * @return true if there are more permutations, otherwise false + */ + public boolean hasNext() { + return nextPermutation != null; + } + + /** + * Returns the next permutation of the input collection. + * @return a list of the permutator's elements representing a permutation + * @throws NoSuchElementException if there are no more permutations + */ + public List next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + // find the largest mobile integer k + int indexOfLargestMobileInteger = -1; + int largestKey = -1; + for (int i = 0; i < keys.length; i++) { + if ((direction[i] && i < keys.length - 1 && keys[i] > keys[i + 1]) || + (!direction[i] && i > 0 && keys[i] > keys[i - 1])) { + if (keys[i] > largestKey) { + largestKey = keys[i]; + indexOfLargestMobileInteger = i; + } + } + } + if (largestKey == -1) { + List toReturn = nextPermutation; + nextPermutation = null; + return toReturn; + } + + // swap k and the adjacent integer it is looking at + final int offset = direction[indexOfLargestMobileInteger] ? 1 : -1; + final int tmpKey = keys[indexOfLargestMobileInteger]; + keys[indexOfLargestMobileInteger] = keys[indexOfLargestMobileInteger + offset]; + keys[indexOfLargestMobileInteger + offset] = tmpKey; + boolean tmpDirection = direction[indexOfLargestMobileInteger]; + direction[indexOfLargestMobileInteger] = direction[indexOfLargestMobileInteger + offset]; + direction[indexOfLargestMobileInteger + offset] = tmpDirection; + + // reverse the direction of all integers larger than k and build the result + final List nextP = new ArrayList(); + for (int i = 0; i < keys.length; i++) { + if (keys[i] > largestKey) { + direction[i] = !direction[i]; + } + nextP.add(objectMap.get(Integer.valueOf(keys[i]))); + } + final List result = nextPermutation; + nextPermutation = nextP; + return result; + } + + public void remove() { + throw new UnsupportedOperationException("remove() is not supported"); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/PushbackIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/PushbackIterator.java new file mode 100644 index 000000000..a177955ed --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/PushbackIterator.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; + +/** + * Decorates an iterator to support pushback of elements. + *

              + * The decorator stores the pushed back elements in a LIFO manner: the last element + * that has been pushed back, will be returned as the next element in a call to {@link #next()}. + *

              + * The decorator does not support the removal operation. Any call to {@link #remove()} will + * result in an {@link UnsupportedOperationException}. + * + * @since 4.0 + * @version $Id: PushbackIterator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PushbackIterator implements Iterator { + + /** The iterator being decorated. */ + private final Iterator iterator; + + /** The LIFO queue containing the pushed back items. */ + private Deque items = new ArrayDeque(); + + //----------------------------------------------------------------------- + /** + * Decorates the specified iterator to support one-element lookahead. + *

              + * If the iterator is already a {@link PushbackIterator} it is returned directly. + * + * @param the element type + * @param iterator the iterator to decorate + * @return a new peeking iterator + * @throws NullPointerException if the iterator is null + */ + public static PushbackIterator pushbackIterator(final Iterator iterator) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + if (iterator instanceof PushbackIterator) { + @SuppressWarnings("unchecked") // safe cast + final PushbackIterator it = (PushbackIterator) iterator; + return it; + } + return new PushbackIterator(iterator); + } + + //----------------------------------------------------------------------- + + /** + * Constructor. + * + * @param iterator the iterator to decorate + */ + public PushbackIterator(final Iterator iterator) { + super(); + this.iterator = iterator; + } + + /** + * Push back the given element to the iterator. + *

              + * Calling {@link #next()} immediately afterwards will return exactly this element. + * + * @param item the element to push back to the iterator + */ + public void pushback(final E item) { + items.push(item); + } + + public boolean hasNext() { + return !items.isEmpty() ? true : iterator.hasNext(); + } + + public E next() { + return !items.isEmpty() ? items.pop() : iterator.next(); + } + + /** + * This iterator will always throw an {@link UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + */ + public void remove() { + throw new UnsupportedOperationException(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ReverseListIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ReverseListIterator.java new file mode 100644 index 000000000..fdbc50f23 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ReverseListIterator.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.collections4.ResettableListIterator; + +/** + * Iterates backwards through a List, starting with the last element + * and continuing to the first. This is useful for looping around + * a list in reverse order without needing to actually reverse the list. + *

              + * The first call to next() will return the last element + * from the list, and so on. The hasNext() method works + * in concert with the next() method as expected. + * However, the nextIndex() method returns the correct + * index in the list, thus it starts high and reduces as the iteration + * continues. The previous methods work similarly. + * + * @since 3.2 + * @version $Id: ReverseListIterator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class ReverseListIterator implements ResettableListIterator { + + /** The list being wrapped. */ + private final List list; + /** The list iterator being wrapped. */ + private ListIterator iterator; + /** Flag to indicate if updating is possible at the moment. */ + private boolean validForUpdate = true; + + /** + * Constructor that wraps a list. + * + * @param list the list to create a reversed iterator for + * @throws NullPointerException if the list is null + */ + public ReverseListIterator(final List list) { + super(); + if (list == null) { + throw new NullPointerException("List must not be null."); + } + this.list = list; + iterator = list.listIterator(list.size()); + } + + //----------------------------------------------------------------------- + /** + * Checks whether there is another element. + * + * @return true if there is another element + */ + public boolean hasNext() { + return iterator.hasPrevious(); + } + + /** + * Gets the next element. + * The next element is the previous in the list. + * + * @return the next element in the iterator + */ + public E next() { + final E obj = iterator.previous(); + validForUpdate = true; + return obj; + } + + /** + * Gets the index of the next element. + * + * @return the index of the next element in the iterator + */ + public int nextIndex() { + return iterator.previousIndex(); + } + + /** + * Checks whether there is a previous element. + * + * @return true if there is a previous element + */ + public boolean hasPrevious() { + return iterator.hasNext(); + } + + /** + * Gets the previous element. + * The next element is the previous in the list. + * + * @return the previous element in the iterator + */ + public E previous() { + final E obj = iterator.next(); + validForUpdate = true; + return obj; + } + + /** + * Gets the index of the previous element. + * + * @return the index of the previous element in the iterator + */ + public int previousIndex() { + return iterator.nextIndex(); + } + + /** + * Removes the last returned element. + * + * @throws UnsupportedOperationException if the list is unmodifiable + * @throws IllegalStateException if there is no element to remove + */ + public void remove() { + if (validForUpdate == false) { + throw new IllegalStateException("Cannot remove from list until next() or previous() called"); + } + iterator.remove(); + } + + /** + * Replaces the last returned element. + * + * @param obj the object to set + * @throws UnsupportedOperationException if the list is unmodifiable + * @throws IllegalStateException if the iterator is not in a valid state for set + */ + public void set(final E obj) { + if (validForUpdate == false) { + throw new IllegalStateException("Cannot set to list until next() or previous() called"); + } + iterator.set(obj); + } + + /** + * Adds a new element to the list between the next and previous elements. + * + * @param obj the object to add + * @throws UnsupportedOperationException if the list is unmodifiable + * @throws IllegalStateException if the iterator is not in a valid state for set + */ + public void add(final E obj) { + // the validForUpdate flag is needed as the necessary previous() + // method call re-enables remove and add + if (validForUpdate == false) { + throw new IllegalStateException("Cannot add to list until next() or previous() called"); + } + validForUpdate = false; + iterator.add(obj); + iterator.previous(); + } + + /** + * Resets the iterator back to the start (which is the + * end of the list as this is a reversed iterator) + */ + public void reset() { + iterator = list.listIterator(list.size()); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/SingletonIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/SingletonIterator.java new file mode 100644 index 000000000..52606957e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/SingletonIterator.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.ResettableIterator; + +/** + * SingletonIterator is an {@link Iterator} over a single + * object instance. + * + * @since 2.0 + * @version $Id: SingletonIterator.java 1612021 2014-07-20 04:51:05Z ggregory $ + */ +public class SingletonIterator + implements ResettableIterator { + + /** Whether remove is allowed */ + private final boolean removeAllowed; + /** Is the cursor before the first element */ + private boolean beforeFirst = true; + /** Has the element been removed */ + private boolean removed = false; + /** The object */ + private E object; + + /** + * Constructs a new SingletonIterator where remove + * is a permitted operation. + * + * @param object the single object to return from the iterator + */ + public SingletonIterator(final E object) { + this(object, true); + } + + /** + * Constructs a new SingletonIterator optionally choosing if + * remove is a permitted operation. + * + * @param object the single object to return from the iterator + * @param removeAllowed true if remove is allowed + * @since 3.1 + */ + public SingletonIterator(final E object, final boolean removeAllowed) { + super(); + this.object = object; + this.removeAllowed = removeAllowed; + } + + //----------------------------------------------------------------------- + /** + * Is another object available from the iterator? + *

              + * This returns true if the single object hasn't been returned yet. + * + * @return true if the single object hasn't been returned yet + */ + public boolean hasNext() { + return beforeFirst && !removed; + } + + /** + * Get the next object from the iterator. + *

              + * This returns the single object if it hasn't been returned yet. + * + * @return the single object + * @throws NoSuchElementException if the single object has already + * been returned + */ + public E next() { + if (!beforeFirst || removed) { + throw new NoSuchElementException(); + } + beforeFirst = false; + return object; + } + + /** + * Remove the object from this iterator. + * + * @throws IllegalStateException if the {@code next} method has not + * yet been called, or the {@code remove} method has already + * been called after the last call to the {@code next} + * method. + * @throws UnsupportedOperationException if remove is not supported + */ + public void remove() { + if (removeAllowed) { + if (removed || beforeFirst) { + throw new IllegalStateException(); + } + object = null; + removed = true; + } else { + throw new UnsupportedOperationException(); + } + } + + /** + * Reset the iterator to the start. + */ + public void reset() { + beforeFirst = true; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/SingletonListIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/SingletonListIterator.java new file mode 100644 index 000000000..20f9b5a35 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/SingletonListIterator.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.ResettableListIterator; + +/** + * SingletonIterator is an {@link ListIterator} over a single + * object instance. + * + * @since 2.1 + * @version $Id: SingletonListIterator.java 1612021 2014-07-20 04:51:05Z ggregory $ + */ +public class SingletonListIterator implements ResettableListIterator { + + private boolean beforeFirst = true; + private boolean nextCalled = false; + private boolean removed = false; + private E object; + + /** + * Constructs a new SingletonListIterator. + * + * @param object the single object to return from the iterator + */ + public SingletonListIterator(final E object) { + super(); + this.object = object; + } + + /** + * Is another object available from the iterator? + *

              + * This returns true if the single object hasn't been returned yet. + * + * @return true if the single object hasn't been returned yet + */ + public boolean hasNext() { + return beforeFirst && !removed; + } + + /** + * Is a previous object available from the iterator? + *

              + * This returns true if the single object has been returned. + * + * @return true if the single object has been returned + */ + public boolean hasPrevious() { + return !beforeFirst && !removed; + } + + /** + * Returns the index of the element that would be returned by a subsequent + * call to {@code next}. + * + * @return 0 or 1 depending on current state. + */ + public int nextIndex() { + return beforeFirst ? 0 : 1; + } + + /** + * Returns the index of the element that would be returned by a subsequent + * call to {@code previous}. A return value of -1 indicates that the iterator is currently at + * the start. + * + * @return 0 or -1 depending on current state. + */ + public int previousIndex() { + return beforeFirst ? -1 : 0; + } + + /** + * Get the next object from the iterator. + *

              + * This returns the single object if it hasn't been returned yet. + * + * @return the single object + * @throws NoSuchElementException if the single object has already + * been returned + */ + public E next() { + if (!beforeFirst || removed) { + throw new NoSuchElementException(); + } + beforeFirst = false; + nextCalled = true; + return object; + } + + /** + * Get the previous object from the iterator. + *

              + * This returns the single object if it has been returned. + * + * @return the single object + * @throws NoSuchElementException if the single object has not already + * been returned + */ + public E previous() { + if (beforeFirst || removed) { + throw new NoSuchElementException(); + } + beforeFirst = true; + return object; + } + + /** + * Remove the object from this iterator. + * @throws IllegalStateException if the {@code next} or {@code previous} + * method has not yet been called, or the {@code remove} method + * has already been called after the last call to {@code next} + * or {@code previous}. + */ + public void remove() { + if(!nextCalled || removed) { + throw new IllegalStateException(); + } + object = null; + removed = true; + } + + /** + * Add always throws {@link UnsupportedOperationException}. + * + * @param obj the object to add + * @throws UnsupportedOperationException always + */ + public void add(final E obj) { + throw new UnsupportedOperationException("add() is not supported by this iterator"); + } + + /** + * Set sets the value of the singleton. + * + * @param obj the object to set + * @throws IllegalStateException if {@code next} has not been called + * or the object has been removed + */ + public void set(final E obj) { + if (!nextCalled || removed) { + throw new IllegalStateException(); + } + this.object = obj; + } + + /** + * Reset the iterator back to the start. + */ + public void reset() { + beforeFirst = true; + nextCalled = false; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/SkippingIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/SkippingIterator.java new file mode 100644 index 000000000..bee201f0e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/SkippingIterator.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law + * or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; + +/** + * Decorates another iterator to skip the first N elements. + *

              + * In case an offset parameter other than 0 is provided, the decorated + * iterator is immediately advanced to this position, skipping all elements + * before that position. + * + * @since 4.1 + * @version $Id: SkippingIterator.java 1685902 2015-06-16 20:13:13Z tn $ + */ +public class SkippingIterator extends AbstractIteratorDecorator { + + /** The offset to bound the first element return */ + private final long offset; + + /** The position of the current element */ + private long pos; + + //----------------------------------------------------------------------- + + /** + * Decorates the specified iterator to skip all elements until the iterator + * reaches the position at {@code offset}. + *

              + * The iterator is immediately advanced until it reaches the position at {@code offset}, + * incurring O(n) time. + * + * @param iterator the iterator to be decorated + * @param offset the index of the first element of the decorated iterator to return + * @throws NullPointerException if iterator is null + * @throws IllegalArgumentException if offset is negative + */ + public SkippingIterator(final Iterator iterator, final long offset) { + super(iterator); + + if (offset < 0) { + throw new IllegalArgumentException("Offset parameter must not be negative."); + } + + this.offset = offset; + this.pos = 0; + init(); + } + + /** + * Skips the given number of elements. + */ + private void init() { + while (pos < offset && hasNext()) { + next(); + } + } + + //----------------------------------------------------------------------- + + @Override + public E next() { + final E next = super.next(); + pos++; + return next; + } + + /** + * {@inheritDoc} + *

              + * In case an offset other than 0 was specified, the underlying iterator will be advanced + * to this position upon creation. A call to {@link #remove()} will still result in an + * {@link IllegalStateException} if no explicit call to {@link #next()} has been made prior + * to calling {@link #remove()}. + */ + @Override + public void remove() { + if (pos <= offset) { + throw new IllegalStateException("remove() can not be called before calling next()"); + } + super.remove(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/TransformIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/TransformIterator.java new file mode 100644 index 000000000..554bab69f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/TransformIterator.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; + +import org.apache.commons.collections4.Transformer; + +/** + * Decorates an iterator such that each element returned is transformed. + * + * @since 1.0 + * @version $Id: TransformIterator.java 1477802 2013-04-30 20:01:28Z tn $ + */ +public class TransformIterator implements Iterator { + + /** The iterator being used */ + private Iterator iterator; + /** The transformer being used */ + private Transformer transformer; + + //----------------------------------------------------------------------- + /** + * Constructs a new TransformIterator that will not function + * until the {@link #setIterator(Iterator) setIterator} and + * {@link #setTransformer(Transformer)} methods are invoked. + */ + public TransformIterator() { + super(); + } + + /** + * Constructs a new TransformIterator that won't transform + * elements from the given iterator. + * + * @param iterator the iterator to use + */ + public TransformIterator(final Iterator iterator) { + super(); + this.iterator = iterator; + } + + /** + * Constructs a new TransformIterator that will use the + * given iterator and transformer. If the given transformer is null, + * then objects will not be transformed. + * + * @param iterator the iterator to use + * @param transformer the transformer to use + */ + public TransformIterator(final Iterator iterator, + final Transformer transformer) { + super(); + this.iterator = iterator; + this.transformer = transformer; + } + + //----------------------------------------------------------------------- + public boolean hasNext() { + return iterator.hasNext(); + } + + /** + * Gets the next object from the iteration, transforming it using the + * current transformer. If the transformer is null, no transformation + * occurs and the object from the iterator is returned directly. + * + * @return the next object + * @throws java.util.NoSuchElementException if there are no more elements + */ + public O next() { + return transform(iterator.next()); + } + + public void remove() { + iterator.remove(); + } + + //----------------------------------------------------------------------- + /** + * Gets the iterator this iterator is using. + * + * @return the iterator. + */ + public Iterator getIterator() { + return iterator; + } + + /** + * Sets the iterator for this iterator to use. + * If iteration has started, this effectively resets the iterator. + * + * @param iterator the iterator to use + */ + public void setIterator(final Iterator iterator) { + this.iterator = iterator; + } + + //----------------------------------------------------------------------- + /** + * Gets the transformer this iterator is using. + * + * @return the transformer. + */ + public Transformer getTransformer() { + return transformer; + } + + /** + * Sets the transformer this the iterator to use. + * A null transformer is a no-op transformer. + * + * @param transformer the transformer to use + */ + public void setTransformer(final Transformer transformer) { + this.transformer = transformer; + } + + //----------------------------------------------------------------------- + /** + * Transforms the given object using the transformer. + * If the transformer is null, the original object is returned as-is. + * + * @param source the object to transform + * @return the transformed object + */ + protected O transform(final I source) { + return transformer.transform(source); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UniqueFilterIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UniqueFilterIterator.java new file mode 100644 index 000000000..420e7ce52 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UniqueFilterIterator.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; + +import org.apache.commons.collections4.functors.UniquePredicate; + +/** + * A FilterIterator which only returns "unique" Objects. Internally, + * the Iterator maintains a Set of objects it has already encountered, + * and duplicate Objects are skipped. + * + * @since 2.1 + * @version $Id: UniqueFilterIterator.java 1479385 2013-05-05 20:31:06Z tn $ + */ +public class UniqueFilterIterator extends FilterIterator { + + //------------------------------------------------------------------------- + + /** + * Constructs a new UniqueFilterIterator. + * + * @param iterator the iterator to use + */ + public UniqueFilterIterator(final Iterator iterator) { + super(iterator, UniquePredicate.uniquePredicate()); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableIterator.java new file mode 100644 index 000000000..50fede344 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableIterator.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.Iterator; + +import org.apache.commons.collections4.Unmodifiable; + +/** + * Decorates an iterator such that it cannot be modified. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableIterator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableIterator implements Iterator, Unmodifiable { + + /** The iterator being decorated */ + private final Iterator iterator; + + //----------------------------------------------------------------------- + /** + * Decorates the specified iterator such that it cannot be modified. + *

              + * If the iterator is already unmodifiable it is returned directly. + * + * @param the element type + * @param iterator the iterator to decorate + * @return a new unmodifiable iterator + * @throws NullPointerException if the iterator is null + */ + public static Iterator unmodifiableIterator(final Iterator iterator) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + if (iterator instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final Iterator tmpIterator = (Iterator) iterator; + return tmpIterator; + } + return new UnmodifiableIterator(iterator); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param iterator the iterator to decorate + */ + private UnmodifiableIterator(final Iterator iterator) { + super(); + this.iterator = iterator; + } + + //----------------------------------------------------------------------- + public boolean hasNext() { + return iterator.hasNext(); + } + + public E next() { + return iterator.next(); + } + + public void remove() { + throw new UnsupportedOperationException("remove() is not supported"); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableListIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableListIterator.java new file mode 100644 index 000000000..20cece1a5 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableListIterator.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.ListIterator; + +import org.apache.commons.collections4.Unmodifiable; + +/** + * Decorates a list iterator such that it cannot be modified. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableListIterator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableListIterator implements ListIterator, Unmodifiable { + + /** The iterator being decorated */ + private final ListIterator iterator; + + //----------------------------------------------------------------------- + /** + * Decorates the specified iterator such that it cannot be modified. + * + * @param the element type + * @param iterator the iterator to decorate + * @return a new unmodifiable list iterator + * @throws NullPointerException if the iterator is null + */ + public static ListIterator umodifiableListIterator(final ListIterator iterator) { + if (iterator == null) { + throw new NullPointerException("ListIterator must not be null"); + } + if (iterator instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final ListIterator tmpIterator = (ListIterator) iterator; + return tmpIterator; + } + return new UnmodifiableListIterator(iterator); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param iterator the iterator to decorate + */ + private UnmodifiableListIterator(final ListIterator iterator) { + super(); + this.iterator = iterator; + } + + //----------------------------------------------------------------------- + public boolean hasNext() { + return iterator.hasNext(); + } + + public E next() { + return iterator.next(); + } + + public int nextIndex() { + return iterator.nextIndex(); + } + + public boolean hasPrevious() { + return iterator.hasPrevious(); + } + + public E previous() { + return iterator.previous(); + } + + public int previousIndex() { + return iterator.previousIndex(); + } + + public void remove() { + throw new UnsupportedOperationException("remove() is not supported"); + } + + public void set(final E obj) { + throw new UnsupportedOperationException("set() is not supported"); + } + + public void add(final E obj) { + throw new UnsupportedOperationException("add() is not supported"); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableMapIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableMapIterator.java new file mode 100644 index 000000000..80bc1a081 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableMapIterator.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.Unmodifiable; + +/** + * Decorates a map iterator such that it cannot be modified. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableMapIterator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableMapIterator implements MapIterator, Unmodifiable { + + /** The iterator being decorated */ + private final MapIterator iterator; + + //----------------------------------------------------------------------- + /** + * Decorates the specified iterator such that it cannot be modified. + * + * @param the key type + * @param the value type + * @param iterator the iterator to decorate + * @return a new unmodifiable map iterator + * @throws NullPointerException if the iterator is null + */ + public static MapIterator unmodifiableMapIterator( + final MapIterator iterator) { + if (iterator == null) { + throw new NullPointerException("MapIterator must not be null"); + } + if (iterator instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final MapIterator tmpIterator = (MapIterator) iterator; + return tmpIterator; + } + return new UnmodifiableMapIterator(iterator); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param iterator the iterator to decorate + */ + private UnmodifiableMapIterator(final MapIterator iterator) { + super(); + this.iterator = iterator; + } + + //----------------------------------------------------------------------- + public boolean hasNext() { + return iterator.hasNext(); + } + + public K next() { + return iterator.next(); + } + + public K getKey() { + return iterator.getKey(); + } + + public V getValue() { + return iterator.getValue(); + } + + public V setValue(final V value) { + throw new UnsupportedOperationException("setValue() is not supported"); + } + + public void remove() { + throw new UnsupportedOperationException("remove() is not supported"); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableOrderedMapIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableOrderedMapIterator.java new file mode 100644 index 000000000..d381d89ea --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/UnmodifiableOrderedMapIterator.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.Unmodifiable; + +/** + * Decorates an ordered map iterator such that it cannot be modified. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableOrderedMapIterator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableOrderedMapIterator implements OrderedMapIterator, + Unmodifiable { + + /** The iterator being decorated */ + private final OrderedMapIterator iterator; + + //----------------------------------------------------------------------- + /** + * Decorates the specified iterator such that it cannot be modified. + * + * @param the key type + * @param the value type + * @param iterator the iterator to decorate + * @return a new unmodifiable ordered map iterator + * @throws NullPointerException if the iterator is null + */ + public static OrderedMapIterator unmodifiableOrderedMapIterator( + final OrderedMapIterator iterator) { + + if (iterator == null) { + throw new NullPointerException("OrderedMapIterator must not be null"); + } + if (iterator instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final OrderedMapIterator tmpIterator = (OrderedMapIterator) iterator; + return tmpIterator; + } + return new UnmodifiableOrderedMapIterator(iterator); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param iterator the iterator to decorate + */ + private UnmodifiableOrderedMapIterator(final OrderedMapIterator iterator) { + super(); + this.iterator = iterator; + } + + //----------------------------------------------------------------------- + public boolean hasNext() { + return iterator.hasNext(); + } + + public K next() { + return iterator.next(); + } + + public boolean hasPrevious() { + return iterator.hasPrevious(); + } + + public K previous() { + return iterator.previous(); + } + + public K getKey() { + return iterator.getKey(); + } + + public V getValue() { + return iterator.getValue(); + } + + public V setValue(final V value) { + throw new UnsupportedOperationException("setValue() is not supported"); + } + + public void remove() { + throw new UnsupportedOperationException("remove() is not supported"); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ZippingIterator.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ZippingIterator.java new file mode 100644 index 000000000..f9d301557 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/ZippingIterator.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.iterators; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.FluentIterable; + +/** + * Provides an interleaved iteration over the elements contained in a + * collection of Iterators. + *

              + * Given two {@link Iterator} instances {@code A} and {@code B}, the + * {@link #next} method on this iterator will switch between {@code A.next()} + * and {@code B.next()} until both iterators are exhausted. + * + * @since 4.1 + * @version $Id: ZippingIterator.java 1682196 2015-05-28 09:58:09Z tn $ + */ +public class ZippingIterator implements Iterator { + + /** The {@link Iterator}s to evaluate. */ + private final Iterator> iterators; + + /** The next iterator to use for next(). */ + private Iterator nextIterator = null; + + /** The last iterator which was used for next(). */ + private Iterator lastReturned = null; + + // Constructors + // ---------------------------------------------------------------------- + + /** + * Constructs a new ZippingIterator that will provide + * interleaved iteration over the two given iterators. + * + * @param a the first child iterator + * @param b the second child iterator + * @throws NullPointerException if either iterator is null + */ + @SuppressWarnings("unchecked") + public ZippingIterator(final Iterator a, final Iterator b) { + this(new Iterator[] {a, b}); + } + + /** + * Constructs a new ZippingIterator that will provide + * interleaved iteration over the three given iterators. + * + * @param a the first child iterator + * @param b the second child iterator + * @param c the third child iterator + * @throws NullPointerException if either iterator is null + */ + @SuppressWarnings("unchecked") + public ZippingIterator(final Iterator a, + final Iterator b, + final Iterator c) { + this(new Iterator[] {a, b, c}); + } + + /** + * Constructs a new ZippingIterator that will provide + * interleaved iteration of the specified iterators. + * + * @param iterators the array of iterators + * @throws NullPointerException if any iterator is null + */ + public ZippingIterator(final Iterator... iterators) { + // create a mutable list to be able to remove exhausted iterators + final List> list = new ArrayList>(); + for (final Iterator iterator : iterators) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null."); + } + list.add(iterator); + } + this.iterators = FluentIterable.of(list).loop().iterator(); + } + + // Iterator Methods + // ------------------------------------------------------------------- + + /** + * Returns {@code true} if any child iterator has remaining elements. + * + * @return true if this iterator has remaining elements + */ + public boolean hasNext() { + // the next iterator has already been determined + // this might happen if hasNext() is called multiple + if (nextIterator != null) { + return true; + } + + while(iterators.hasNext()) { + final Iterator childIterator = iterators.next(); + if (childIterator.hasNext()) { + nextIterator = childIterator; + return true; + } else { + // iterator is exhausted, remove it + iterators.remove(); + } + } + return false; + } + + /** + * Returns the next element from a child iterator. + * + * @return the next interleaved element + * @throws NoSuchElementException if no child iterator has any more elements + */ + public E next() throws NoSuchElementException { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final E val = nextIterator.next(); + lastReturned = nextIterator; + nextIterator = null; + return val; + } + + /** + * Removes the last returned element from the child iterator that produced it. + * + * @throws IllegalStateException if there is no last returned element, or if + * the last returned element has already been removed + */ + public void remove() { + if (lastReturned == null) { + throw new IllegalStateException("No value can be removed at present"); + } + lastReturned.remove(); + lastReturned = null; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/iterators/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/package-info.java new file mode 100644 index 000000000..fc5547ead --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/iterators/package-info.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the + * {@link java.util.Iterator Iterator} interface. + *

              + * You may also consider using + * {@link org.apache.commons.collections4.IteratorUtils IteratorUtils}, + * which is a single class that uses static methods to construct instances + * of the classes in this package. + * + * @version $Id: package-info.java 1477802 2013-04-30 20:01:28Z tn $ + */ +package org.apache.commons.collections4.iterators; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractKeyValue.java b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractKeyValue.java new file mode 100644 index 000000000..e85662657 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractKeyValue.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.keyvalue; + +import org.apache.commons.collections4.KeyValue; + +/** + * Abstract pair class to assist with creating KeyValue + * and {@link java.util.Map.Entry Map.Entry} implementations. + * + * @since 3.0 + * @version $Id: AbstractKeyValue.java 1477753 2013-04-30 18:24:24Z tn $ + */ +public abstract class AbstractKeyValue implements KeyValue { + + /** The key */ + private K key; + /** The value */ + private V value; + + /** + * Constructs a new pair with the specified key and given value. + * + * @param key the key for the entry, may be null + * @param value the value for the entry, may be null + */ + protected AbstractKeyValue(final K key, final V value) { + super(); + this.key = key; + this.value = value; + } + + /** + * Gets the key from the pair. + * + * @return the key + */ + public K getKey() { + return key; + } + + protected K setKey(K key) { + final K old = this.key; + this.key = key; + return old; + } + + /** + * Gets the value from the pair. + * + * @return the value + */ + public V getValue() { + return value; + } + + protected V setValue(V value) { + final V old = this.value; + this.value = value; + return old; + } + + /** + * Gets a debugging String view of the pair. + * + * @return a String view of the entry + */ + @Override + public String toString() { + return new StringBuilder() + .append(getKey()) + .append('=') + .append(getValue()) + .toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractMapEntry.java b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractMapEntry.java new file mode 100644 index 000000000..e9d3d0f60 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractMapEntry.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.keyvalue; + +import java.util.Map; + +/** + * Abstract Pair class to assist with creating correct + * {@link java.util.Map.Entry Map.Entry} implementations. + * + * @since 3.0 + * @version $Id: AbstractMapEntry.java 1477753 2013-04-30 18:24:24Z tn $ + */ +public abstract class AbstractMapEntry extends AbstractKeyValue implements Map.Entry { + + /** + * Constructs a new entry with the given key and given value. + * + * @param key the key for the entry, may be null + * @param value the value for the entry, may be null + */ + protected AbstractMapEntry(final K key, final V value) { + super(key, value); + } + + // Map.Entry interface + //------------------------------------------------------------------------- + /** + * Sets the value stored in this Map.Entry. + *

              + * This Map.Entry is not connected to a Map, so only the + * local data is changed. + * + * @param value the new value + * @return the previous value + */ + @Override + public V setValue(final V value) { + return super.setValue(value); + } + + /** + * Compares this Map.Entry with another Map.Entry. + *

              + * Implemented per API documentation of {@link java.util.Map.Entry#equals(Object)} + * + * @param obj the object to compare to + * @return true if equal key and value + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry other = (Map.Entry) obj; + return + (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) && + (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue())); + } + + /** + * Gets a hashCode compatible with the equals method. + *

              + * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()} + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return (getKey() == null ? 0 : getKey().hashCode()) ^ + (getValue() == null ? 0 : getValue().hashCode()); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractMapEntryDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractMapEntryDecorator.java new file mode 100644 index 000000000..8dc36d340 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/AbstractMapEntryDecorator.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.keyvalue; + +import java.util.Map; + +import org.apache.commons.collections4.KeyValue; + +/** + * Provides a base decorator that allows additional functionality to be + * added to a {@link java.util.Map.Entry Map.Entry}. + * + * @since 3.0 + * @version $Id: AbstractMapEntryDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue { + + /** The Map.Entry to decorate */ + private final Map.Entry entry; + + /** + * Constructor that wraps (not copies). + * + * @param entry the Map.Entry to decorate, must not be null + * @throws NullPointerException if the collection is null + */ + public AbstractMapEntryDecorator(final Map.Entry entry) { + if (entry == null) { + throw new NullPointerException("Map Entry must not be null."); + } + this.entry = entry; + } + + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + protected Map.Entry getMapEntry() { + return entry; + } + + //----------------------------------------------------------------------- + + public K getKey() { + return entry.getKey(); + } + + public V getValue() { + return entry.getValue(); + } + + public V setValue(final V object) { + return entry.setValue(object); + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + return entry.equals(object); + } + + @Override + public int hashCode() { + return entry.hashCode(); + } + + @Override + public String toString() { + return entry.toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/DefaultKeyValue.java b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/DefaultKeyValue.java new file mode 100644 index 000000000..06054cc57 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/DefaultKeyValue.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.keyvalue; + +import java.util.Map; + +import org.apache.commons.collections4.KeyValue; + +/** + * A mutable KeyValue pair that does not implement + * {@link java.util.Map.Entry Map.Entry}. + *

              + * Note that a DefaultKeyValue instance may not contain + * itself as a key or value. + * + * @since 3.0 + * @version $Id: DefaultKeyValue.java 1533984 2013-10-20 21:12:51Z tn $ + */ +public class DefaultKeyValue extends AbstractKeyValue { + + /** + * Constructs a new pair with a null key and null value. + */ + public DefaultKeyValue() { + super(null, null); + } + + /** + * Constructs a new pair with the specified key and given value. + * + * @param key the key for the entry, may be null + * @param value the value for the entry, may be null + */ + public DefaultKeyValue(final K key, final V value) { + super(key, value); + } + + /** + * Constructs a new pair from the specified KeyValue. + * + * @param pair the pair to copy, must not be null + * @throws NullPointerException if the entry is null + */ + public DefaultKeyValue(final KeyValue pair) { + super(pair.getKey(), pair.getValue()); + } + + /** + * Constructs a new pair from the specified Map.Entry. + * + * @param entry the entry to copy, must not be null + * @throws NullPointerException if the entry is null + */ + public DefaultKeyValue(final Map.Entry entry) { + super(entry.getKey(), entry.getValue()); + } + + //----------------------------------------------------------------------- + /** + * Sets the key. + * + * @param key the new key + * @return the old key + * @throws IllegalArgumentException if key is this object + */ + @Override + public K setKey(final K key) { + if (key == this) { + throw new IllegalArgumentException("DefaultKeyValue may not contain itself as a key."); + } + + return super.setKey(key); + } + + /** + * Sets the value. + * + * @return the old value of the value + * @param value the new value + * @throws IllegalArgumentException if value is this object + */ + @Override + public V setValue(final V value) { + if (value == this) { + throw new IllegalArgumentException("DefaultKeyValue may not contain itself as a value."); + } + + return super.setValue(value); + } + + //----------------------------------------------------------------------- + /** + * Returns a new Map.Entry object with key and value from this pair. + * + * @return a MapEntry instance + */ + public Map.Entry toMapEntry() { + return new DefaultMapEntry(this); + } + + //----------------------------------------------------------------------- + /** + * Compares this Map.Entry with another Map.Entry. + *

              + * Returns true if the compared object is also a DefaultKeyValue, + * and its key and value are equal to this object's key and value. + * + * @param obj the object to compare to + * @return true if equal key and value + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof DefaultKeyValue == false) { + return false; + } + + final DefaultKeyValue other = (DefaultKeyValue) obj; + return + (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) && + (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue())); + } + + /** + * Gets a hashCode compatible with the equals method. + *

              + * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}, + * however subclasses may override this. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return (getKey() == null ? 0 : getKey().hashCode()) ^ + (getValue() == null ? 0 : getValue().hashCode()); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/DefaultMapEntry.java b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/DefaultMapEntry.java new file mode 100644 index 000000000..893fb5c21 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/DefaultMapEntry.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.keyvalue; + +import java.util.Map; + +import org.apache.commons.collections4.KeyValue; + +/** + * A restricted implementation of {@link java.util.Map.Entry Map.Entry} that prevents + * the {@link java.util.Map.Entry Map.Entry} contract from being broken. + * + * @since 3.0 + * @version $Id: DefaultMapEntry.java 1533984 2013-10-20 21:12:51Z tn $ + */ +public final class DefaultMapEntry extends AbstractMapEntry { + + /** + * Constructs a new entry with the specified key and given value. + * + * @param key the key for the entry, may be null + * @param value the value for the entry, may be null + */ + public DefaultMapEntry(final K key, final V value) { + super(key, value); + } + + /** + * Constructs a new entry from the specified KeyValue. + * + * @param pair the pair to copy, must not be null + * @throws NullPointerException if the entry is null + */ + public DefaultMapEntry(final KeyValue pair) { + super(pair.getKey(), pair.getValue()); + } + + /** + * Constructs a new entry from the specified Map.Entry. + * + * @param entry the entry to copy, must not be null + * @throws NullPointerException if the entry is null + */ + public DefaultMapEntry(final Map.Entry entry) { + super(entry.getKey(), entry.getValue()); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/MultiKey.java b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/MultiKey.java new file mode 100644 index 000000000..7a0b2947c --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/MultiKey.java @@ -0,0 +1,281 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.keyvalue; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * A MultiKey allows multiple map keys to be merged together. + *

              + * The purpose of this class is to avoid the need to write code to handle + * maps of maps. An example might be the need to look up a file name by + * key and locale. The typical solution might be nested maps. This class + * can be used instead by creating an instance passing in the key and locale. + *

              + * Example usage: + *

              + * // populate map with data mapping key+locale to localizedText
              + * Map map = new HashMap();
              + * MultiKey multiKey = new MultiKey(key, locale);
              + * map.put(multiKey, localizedText);
              + *
              + * // later retrieve the localized text
              + * MultiKey multiKey = new MultiKey(key, locale);
              + * String localizedText = (String) map.get(multiKey);
              + * 
              + * + * @since 3.0 + * @version $Id: MultiKey.java 1705620 2015-09-28 08:53:44Z tn $ + */ +public class MultiKey implements Serializable { + // This class could implement List, but that would confuse it's purpose + + /** Serialisation version */ + private static final long serialVersionUID = 4465448607415788805L; + + /** The individual keys */ + private final K[] keys; + /** The cached hashCode */ + private transient int hashCode; + + /** + * Constructor taking two keys. + *

              + * The keys should be immutable + * If they are not then they must not be changed after adding to the MultiKey. + * + * @param key1 the first key + * @param key2 the second key + */ + @SuppressWarnings("unchecked") + public MultiKey(final K key1, final K key2) { + this((K[]) new Object[] { key1, key2 }, false); + } + + /** + * Constructor taking three keys. + *

              + * The keys should be immutable + * If they are not then they must not be changed after adding to the MultiKey. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + */ + @SuppressWarnings("unchecked") + public MultiKey(final K key1, final K key2, final K key3) { + this((K[]) new Object[] {key1, key2, key3}, false); + } + + /** + * Constructor taking four keys. + *

              + * The keys should be immutable + * If they are not then they must not be changed after adding to the MultiKey. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + */ + @SuppressWarnings("unchecked") + public MultiKey(final K key1, final K key2, final K key3, final K key4) { + this((K[]) new Object[] {key1, key2, key3, key4}, false); + } + + /** + * Constructor taking five keys. + *

              + * The keys should be immutable + * If they are not then they must not be changed after adding to the MultiKey. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @param key5 the fifth key + */ + @SuppressWarnings("unchecked") + public MultiKey(final K key1, final K key2, final K key3, final K key4, final K key5) { + this((K[]) new Object[] {key1, key2, key3, key4, key5}, false); + } + + /** + * Constructor taking an array of keys which is cloned. + *

              + * The keys should be immutable + * If they are not then they must not be changed after adding to the MultiKey. + *

              + * This is equivalent to new MultiKey(keys, true). + * + * @param keys the array of keys, not null + * @throws IllegalArgumentException if the key array is null + */ + public MultiKey(final K[] keys) { + this(keys, true); + } + + /** + * Constructor taking an array of keys, optionally choosing whether to clone. + *

              + * If the array is not cloned, then it must not be modified. + *

              + * This method is public for performance reasons only, to avoid a clone. + * The hashcode is calculated once here in this method. + * Therefore, changing the array passed in would not change the hashcode but + * would change the equals method, which is a bug. + *

              + * This is the only fully safe usage of this constructor, as the object array + * is never made available in a variable: + *

              +     * new MultiKey(new Object[] {...}, false);
              +     * 
              + *

              + * The keys should be immutable + * If they are not then they must not be changed after adding to the MultiKey. + * + * @param keys the array of keys, not null + * @param makeClone true to clone the array, false to assign it + * @throws IllegalArgumentException if the key array is null + * @since 3.1 + */ + public MultiKey(final K[] keys, final boolean makeClone) { + super(); + if (keys == null) { + throw new IllegalArgumentException("The array of keys must not be null"); + } + if (makeClone) { + this.keys = keys.clone(); + } else { + this.keys = keys; + } + + calculateHashCode(keys); + } + + //----------------------------------------------------------------------- + /** + * Gets a clone of the array of keys. + *

              + * The keys should be immutable + * If they are not then they must not be changed. + * + * @return the individual keys + */ + public K[] getKeys() { + return keys.clone(); + } + + /** + * Gets the key at the specified index. + *

              + * The key should be immutable. + * If it is not then it must not be changed. + * + * @param index the index to retrieve + * @return the key at the index + * @throws IndexOutOfBoundsException if the index is invalid + * @since 3.1 + */ + public K getKey(final int index) { + return keys[index]; + } + + /** + * Gets the size of the list of keys. + * + * @return the size of the list of keys + * @since 3.1 + */ + public int size() { + return keys.length; + } + + //----------------------------------------------------------------------- + /** + * Compares this object to another. + *

              + * To be equal, the other object must be a MultiKey with the + * same number of keys which are also equal. + * + * @param other the other object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (other instanceof MultiKey) { + final MultiKey otherMulti = (MultiKey) other; + return Arrays.equals(keys, otherMulti.keys); + } + return false; + } + + /** + * Gets the combined hash code that is computed from all the keys. + *

              + * This value is computed once and then cached, so elements should not + * change their hash codes once created (note that this is the same + * constraint that would be used if the individual keys elements were + * themselves {@link java.util.Map Map} keys. + * + * @return the hash code + */ + @Override + public int hashCode() { + return hashCode; + } + + /** + * Gets a debugging string version of the key. + * + * @return a debugging string + */ + @Override + public String toString() { + return "MultiKey" + Arrays.toString(keys); + } + + /** + * Calculate the hash code of the instance using the provided keys. + * @param keys the keys to calculate the hash code for + */ + private void calculateHashCode(final Object[] keys) + { + int total = 0; + for (final Object key : keys) { + if (key != null) { + total ^= key.hashCode(); + } + } + hashCode = total; + } + + /** + * Recalculate the hash code after deserialization. The hash code of some + * keys might have change (hash codes based on the system hash code are + * only stable for the same process). + * @return the instance with recalculated hash code + */ + protected Object readResolve() { + calculateHashCode(keys); + return this; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/TiedMapEntry.java b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/TiedMapEntry.java new file mode 100644 index 000000000..cf2b012c7 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/TiedMapEntry.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.keyvalue; + +import java.io.Serializable; +import java.util.Map; + +import org.apache.commons.collections4.KeyValue; + +/** + * A {@link java.util.Map.Entry Map.Entry} tied to a map underneath. + *

              + * This can be used to enable a map entry to make changes on the underlying + * map, however this will probably mess up any iterators. + * + * @since 3.0 + * @version $Id: TiedMapEntry.java 1477753 2013-04-30 18:24:24Z tn $ + */ +public class TiedMapEntry implements Map.Entry, KeyValue, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = -8453869361373831205L; + + /** The map underlying the entry/iterator */ + private final Map map; + + /** The key */ + private final K key; + + /** + * Constructs a new entry with the given Map and key. + * + * @param map the map + * @param key the key + */ + public TiedMapEntry(final Map map, final K key) { + super(); + this.map = map; + this.key = key; + } + + // Map.Entry interface + //------------------------------------------------------------------------- + /** + * Gets the key of this entry + * + * @return the key + */ + public K getKey() { + return key; + } + + /** + * Gets the value of this entry direct from the map. + * + * @return the value + */ + public V getValue() { + return map.get(key); + } + + /** + * Sets the value associated with the key direct onto the map. + * + * @param value the new value + * @return the old value + * @throws IllegalArgumentException if the value is set to this map entry + */ + public V setValue(final V value) { + if (value == this) { + throw new IllegalArgumentException("Cannot set value to this map entry"); + } + return map.put(key, value); + } + + /** + * Compares this Map.Entry with another Map.Entry. + *

              + * Implemented per API documentation of {@link java.util.Map.Entry#equals(Object)} + * + * @param obj the object to compare to + * @return true if equal key and value + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry other = (Map.Entry) obj; + final Object value = getValue(); + return + (key == null ? other.getKey() == null : key.equals(other.getKey())) && + (value == null ? other.getValue() == null : value.equals(other.getValue())); + } + + /** + * Gets a hashCode compatible with the equals method. + *

              + * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()} + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + final Object value = getValue(); + return (getKey() == null ? 0 : getKey().hashCode()) ^ + (value == null ? 0 : value.hashCode()); + } + + /** + * Gets a string version of the entry. + * + * @return entry as a string + */ + @Override + public String toString() { + return getKey() + "=" + getValue(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/UnmodifiableMapEntry.java b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/UnmodifiableMapEntry.java new file mode 100644 index 000000000..62850bebb --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/UnmodifiableMapEntry.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.keyvalue; + +import java.util.Map; + +import org.apache.commons.collections4.KeyValue; +import org.apache.commons.collections4.Unmodifiable; + +/** + * A {@link java.util.Map.Entry Map.Entry} that throws + * UnsupportedOperationException when setValue is called. + * + * @since 3.0 + * @version $Id: UnmodifiableMapEntry.java 1533984 2013-10-20 21:12:51Z tn $ + */ +public final class UnmodifiableMapEntry extends AbstractMapEntry implements Unmodifiable { + + /** + * Constructs a new entry with the specified key and given value. + * + * @param key the key for the entry, may be null + * @param value the value for the entry, may be null + */ + public UnmodifiableMapEntry(final K key, final V value) { + super(key, value); + } + + /** + * Constructs a new entry from the specified KeyValue. + * + * @param pair the pair to copy, must not be null + * @throws NullPointerException if the entry is null + */ + public UnmodifiableMapEntry(final KeyValue pair) { + super(pair.getKey(), pair.getValue()); + } + + /** + * Constructs a new entry from the specified Map.Entry. + * + * @param entry the entry to copy, must not be null + * @throws NullPointerException if the entry is null + */ + public UnmodifiableMapEntry(final Map.Entry entry) { + super(entry.getKey(), entry.getValue()); + } + + /** + * Throws UnsupportedOperationException. + * + * @param value the new value + * @return the previous value + * @throws UnsupportedOperationException always + */ + @Override + public V setValue(final V value) { + throw new UnsupportedOperationException("setValue() is not supported"); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/package-info.java new file mode 100644 index 000000000..da013fb72 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/keyvalue/package-info.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of collection and map related key/value classes. + * These are usually used in maps, however they can be used as data holders in any collection. + *

              + * The following key/value designs are included: + *

                + *
              • Map Entry - various map entry implementations + *
              • KeyValue - a key and value pair, without map entry semantics + *
              • MultiKey - a holder of multiple keys tied together + *
              + * + * @version $Id: package-info.java 1469004 2013-04-17 17:37:03Z tn $ + */ +package org.apache.commons.collections4.keyvalue; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractLinkedList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractLinkedList.java new file mode 100644 index 000000000..65c6c3ee4 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractLinkedList.java @@ -0,0 +1,1067 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Array; +import java.util.AbstractList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.OrderedIterator; + +/** + * An abstract implementation of a linked list which provides numerous points for + * subclasses to override. + *

              + * Overridable methods are provided to change the storage node and to change how + * nodes are added to and removed. Hopefully, all you need for unusual subclasses + * is here. + * + * @since 3.0 + * @version $Id: AbstractLinkedList.java 1494296 2013-06-18 20:54:29Z tn $ + */ +public abstract class AbstractLinkedList implements List { + + /* + * Implementation notes: + * - a standard circular doubly-linked list + * - a marker node is stored to mark the start and the end of the list + * - node creation and removal always occurs through createNode() and + * removeNode(). + * - a modification count is kept, with the same semantics as + * {@link java.util.LinkedList}. + * - respects {@link AbstractList#modCount} + */ + + /** + * A {@link Node} which indicates the start and end of the list and does not + * hold a value. The value of next is the first item in the + * list. The value of of previous is the last item in the list. + */ + transient Node header; + + /** The size of the list */ + transient int size; + + /** Modification count for iterators */ + transient int modCount; + + /** + * Constructor that does nothing intended for deserialization. + *

              + * If this constructor is used by a serializable subclass then the init() + * method must be called. + */ + protected AbstractLinkedList() { + super(); + } + + /** + * Constructs a list copying data from the specified collection. + * + * @param coll the collection to copy + */ + protected AbstractLinkedList(final Collection coll) { + super(); + init(); + addAll(coll); + } + + /** + * The equivalent of a default constructor, broken out so it can be called + * by any constructor and by readObject. + * Subclasses which override this method should make sure they call super, + * so the list is initialised properly. + */ + protected void init() { + header = createHeaderNode(); + } + + //----------------------------------------------------------------------- + + public int size() { + return size; + } + + public boolean isEmpty() { + return size() == 0; + } + + public E get(final int index) { + final Node node = getNode(index, false); + return node.getValue(); + } + + //----------------------------------------------------------------------- + + public Iterator iterator() { + return listIterator(); + } + + public ListIterator listIterator() { + return new LinkedListIterator(this, 0); + } + + public ListIterator listIterator(final int fromIndex) { + return new LinkedListIterator(this, fromIndex); + } + + //----------------------------------------------------------------------- + + public int indexOf(final Object value) { + int i = 0; + for (Node node = header.next; node != header; node = node.next) { + if (isEqualValue(node.getValue(), value)) { + return i; + } + i++; + } + return -1; + } + + public int lastIndexOf(final Object value) { + int i = size - 1; + for (Node node = header.previous; node != header; node = node.previous) { + if (isEqualValue(node.getValue(), value)) { + return i; + } + i--; + } + return -1; + } + + public boolean contains(final Object value) { + return indexOf(value) != -1; + } + + public boolean containsAll(final Collection coll) { + for (final Object o : coll) { + if (!contains(o)) { + return false; + } + } + return true; + } + + //----------------------------------------------------------------------- + + public Object[] toArray() { + return toArray(new Object[size]); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] array) { + // Extend the array if needed + if (array.length < size) { + final Class componentType = array.getClass().getComponentType(); + array = (T[]) Array.newInstance(componentType, size); + } + // Copy the values into the array + int i = 0; + for (Node node = header.next; node != header; node = node.next, i++) { + array[i] = (T) node.getValue(); + } + // Set the value after the last value to null + if (array.length > size) { + array[size] = null; + } + return array; + } + + /** + * Gets a sublist of the main list. + * + * @param fromIndexInclusive the index to start from + * @param toIndexExclusive the index to end at + * @return the new sublist + */ + public List subList(final int fromIndexInclusive, final int toIndexExclusive) { + return new LinkedSubList(this, fromIndexInclusive, toIndexExclusive); + } + + //----------------------------------------------------------------------- + + public boolean add(final E value) { + addLast(value); + return true; + } + + public void add(final int index, final E value) { + final Node node = getNode(index, true); + addNodeBefore(node, value); + } + + public boolean addAll(final Collection coll) { + return addAll(size, coll); + } + + public boolean addAll(final int index, final Collection coll) { + final Node node = getNode(index, true); + for (final E e : coll) { + addNodeBefore(node, e); + } + return true; + } + + //----------------------------------------------------------------------- + + public E remove(final int index) { + final Node node = getNode(index, false); + final E oldValue = node.getValue(); + removeNode(node); + return oldValue; + } + + public boolean remove(final Object value) { + for (Node node = header.next; node != header; node = node.next) { + if (isEqualValue(node.getValue(), value)) { + removeNode(node); + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + *

              + * This implementation iterates over the elements of this list, checking each element in + * turn to see if it's contained in coll. If it's contained, it's removed + * from this list. As a consequence, it is advised to use a collection type for + * coll that provides a fast (e.g. O(1)) implementation of + * {@link Collection#contains(Object)}. + */ + public boolean removeAll(final Collection coll) { + boolean modified = false; + final Iterator it = iterator(); + while (it.hasNext()) { + if (coll.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + //----------------------------------------------------------------------- + + /** + * {@inheritDoc} + *

              + * This implementation iterates over the elements of this list, checking each element in + * turn to see if it's contained in coll. If it's not contained, it's removed + * from this list. As a consequence, it is advised to use a collection type for + * coll that provides a fast (e.g. O(1)) implementation of + * {@link Collection#contains(Object)}. + */ + public boolean retainAll(final Collection coll) { + boolean modified = false; + final Iterator it = iterator(); + while (it.hasNext()) { + if (coll.contains(it.next()) == false) { + it.remove(); + modified = true; + } + } + return modified; + } + + public E set(final int index, final E value) { + final Node node = getNode(index, false); + final E oldValue = node.getValue(); + updateNode(node, value); + return oldValue; + } + + public void clear() { + removeAllNodes(); + } + + //----------------------------------------------------------------------- + + public E getFirst() { + final Node node = header.next; + if (node == header) { + throw new NoSuchElementException(); + } + return node.getValue(); + } + + public E getLast() { + final Node node = header.previous; + if (node == header) { + throw new NoSuchElementException(); + } + return node.getValue(); + } + + public boolean addFirst(final E o) { + addNodeAfter(header, o); + return true; + } + + public boolean addLast(final E o) { + addNodeBefore(header, o); + return true; + } + + public E removeFirst() { + final Node node = header.next; + if (node == header) { + throw new NoSuchElementException(); + } + final E oldValue = node.getValue(); + removeNode(node); + return oldValue; + } + + public E removeLast() { + final Node node = header.previous; + if (node == header) { + throw new NoSuchElementException(); + } + final E oldValue = node.getValue(); + removeNode(node); + return oldValue; + } + + //----------------------------------------------------------------------- + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof List == false) { + return false; + } + final List other = (List) obj; + if (other.size() != size()) { + return false; + } + final ListIterator it1 = listIterator(); + final ListIterator it2 = other.listIterator(); + while (it1.hasNext() && it2.hasNext()) { + final Object o1 = it1.next(); + final Object o2 = it2.next(); + if (!(o1 == null ? o2 == null : o1.equals(o2))) { + return false; + } + } + return !(it1.hasNext() || it2.hasNext()); + } + + @Override + public int hashCode() { + int hashCode = 1; + for (final E e : this) { + hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode()); + } + return hashCode; + } + + @Override + public String toString() { + if (size() == 0) { + return "[]"; + } + final StringBuilder buf = new StringBuilder(16 * size()); + buf.append('['); + + final Iterator it = iterator(); + boolean hasNext = it.hasNext(); + while (hasNext) { + final Object value = it.next(); + buf.append(value == this ? "(this Collection)" : value); + hasNext = it.hasNext(); + if (hasNext) { + buf.append(", "); + } + } + buf.append(']'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Compares two values for equals. + * This implementation uses the equals method. + * Subclasses can override this to match differently. + * + * @param value1 the first value to compare, may be null + * @param value2 the second value to compare, may be null + * @return true if equal + */ + protected boolean isEqualValue(final Object value1, final Object value2) { + return value1 == value2 || (value1 == null ? false : value1.equals(value2)); + } + + /** + * Updates the node with a new value. + * This implementation sets the value on the node. + * Subclasses can override this to record the change. + * + * @param node node to update + * @param value new value of the node + */ + protected void updateNode(final Node node, final E value) { + node.setValue(value); + } + + /** + * Creates a new node with previous, next and element all set to null. + * This implementation creates a new empty Node. + * Subclasses can override this to create a different class. + * + * @return newly created node + */ + protected Node createHeaderNode() { + return new Node(); + } + + /** + * Creates a new node with the specified properties. + * This implementation creates a new Node with data. + * Subclasses can override this to create a different class. + * + * @param value value of the new node + * @return a new node containing the value + */ + protected Node createNode(final E value) { + return new Node(value); + } + + /** + * Creates a new node with the specified object as its + * value and inserts it before node. + *

              + * This implementation uses {@link #createNode(Object)} and + * {@link #addNode(AbstractLinkedList.Node,AbstractLinkedList.Node)}. + * + * @param node node to insert before + * @param value value of the newly added node + * @throws NullPointerException if node is null + */ + protected void addNodeBefore(final Node node, final E value) { + final Node newNode = createNode(value); + addNode(newNode, node); + } + + /** + * Creates a new node with the specified object as its + * value and inserts it after node. + *

              + * This implementation uses {@link #createNode(Object)} and + * {@link #addNode(AbstractLinkedList.Node,AbstractLinkedList.Node)}. + * + * @param node node to insert after + * @param value value of the newly added node + * @throws NullPointerException if node is null + */ + protected void addNodeAfter(final Node node, final E value) { + final Node newNode = createNode(value); + addNode(newNode, node.next); + } + + /** + * Inserts a new node into the list. + * + * @param nodeToInsert new node to insert + * @param insertBeforeNode node to insert before + * @throws NullPointerException if either node is null + */ + protected void addNode(final Node nodeToInsert, final Node insertBeforeNode) { + nodeToInsert.next = insertBeforeNode; + nodeToInsert.previous = insertBeforeNode.previous; + insertBeforeNode.previous.next = nodeToInsert; + insertBeforeNode.previous = nodeToInsert; + size++; + modCount++; + } + + /** + * Removes the specified node from the list. + * + * @param node the node to remove + * @throws NullPointerException if node is null + */ + protected void removeNode(final Node node) { + node.previous.next = node.next; + node.next.previous = node.previous; + size--; + modCount++; + } + + /** + * Removes all nodes by resetting the circular list marker. + */ + protected void removeAllNodes() { + header.next = header; + header.previous = header; + size = 0; + modCount++; + } + + /** + * Gets the node at a particular index. + * + * @param index the index, starting from 0 + * @param endMarkerAllowed whether or not the end marker can be returned if + * startIndex is set to the list's size + * @return the node at the given index + * @throws IndexOutOfBoundsException if the index is less than 0; equal to + * the size of the list and endMakerAllowed is false; or greater than the + * size of the list + */ + protected Node getNode(final int index, final boolean endMarkerAllowed) throws IndexOutOfBoundsException { + // Check the index is within the bounds + if (index < 0) { + throw new IndexOutOfBoundsException("Couldn't get the node: " + + "index (" + index + ") less than zero."); + } + if (!endMarkerAllowed && index == size) { + throw new IndexOutOfBoundsException("Couldn't get the node: " + + "index (" + index + ") is the size of the list."); + } + if (index > size) { + throw new IndexOutOfBoundsException("Couldn't get the node: " + + "index (" + index + ") greater than the size of the " + + "list (" + size + ")."); + } + // Search the list and get the node + Node node; + if (index < size / 2) { + // Search forwards + node = header.next; + for (int currentIndex = 0; currentIndex < index; currentIndex++) { + node = node.next; + } + } else { + // Search backwards + node = header; + for (int currentIndex = size; currentIndex > index; currentIndex--) { + node = node.previous; + } + } + return node; + } + + //----------------------------------------------------------------------- + /** + * Creates an iterator for the sublist. + * + * @param subList the sublist to get an iterator for + * @return a new iterator on the given sublist + */ + protected Iterator createSubListIterator(final LinkedSubList subList) { + return createSubListListIterator(subList, 0); + } + + /** + * Creates a list iterator for the sublist. + * + * @param subList the sublist to get an iterator for + * @param fromIndex the index to start from, relative to the sublist + * @return a new list iterator on the given sublist + */ + protected ListIterator createSubListListIterator(final LinkedSubList subList, final int fromIndex) { + return new LinkedSubListIterator(subList, fromIndex); + } + + //----------------------------------------------------------------------- + /** + * Serializes the data held in this object to the stream specified. + *

              + * The first serializable subclass must call this method from + * writeObject. + * + * @param outputStream the stream to write the object to + * @throws IOException if anything goes wrong + */ + protected void doWriteObject(final ObjectOutputStream outputStream) throws IOException { + // Write the size so we know how many nodes to read back + outputStream.writeInt(size()); + for (final E e : this) { + outputStream.writeObject(e); + } + } + + /** + * Deserializes the data held in this object to the stream specified. + *

              + * The first serializable subclass must call this method from + * readObject. + * + * @param inputStream the stream to read the object from + * @throws IOException if any error occurs while reading from the stream + * @throws ClassNotFoundException if a class read from the stream can not be loaded + */ + @SuppressWarnings("unchecked") + protected void doReadObject(final ObjectInputStream inputStream) throws IOException, ClassNotFoundException { + init(); + final int size = inputStream.readInt(); + for (int i = 0; i < size; i++) { + add((E) inputStream.readObject()); + } + } + + //----------------------------------------------------------------------- + /** + * A node within the linked list. + *

              + * From Commons Collections 3.1, all access to the value property + * is via the methods on this class. + */ + protected static class Node { + + /** A pointer to the node before this node */ + protected Node previous; + /** A pointer to the node after this node */ + protected Node next; + /** The object contained within this node */ + protected E value; + + /** + * Constructs a new header node. + */ + protected Node() { + super(); + previous = this; + next = this; + } + + /** + * Constructs a new node. + * + * @param value the value to store + */ + protected Node(final E value) { + super(); + this.value = value; + } + + /** + * Constructs a new node. + * + * @param previous the previous node in the list + * @param next the next node in the list + * @param value the value to store + */ + protected Node(final Node previous, final Node next, final E value) { + super(); + this.previous = previous; + this.next = next; + this.value = value; + } + + /** + * Gets the value of the node. + * + * @return the value + * @since 3.1 + */ + protected E getValue() { + return value; + } + + /** + * Sets the value of the node. + * + * @param value the value + * @since 3.1 + */ + protected void setValue(final E value) { + this.value = value; + } + + /** + * Gets the previous node. + * + * @return the previous node + * @since 3.1 + */ + protected Node getPreviousNode() { + return previous; + } + + /** + * Sets the previous node. + * + * @param previous the previous node + * @since 3.1 + */ + protected void setPreviousNode(final Node previous) { + this.previous = previous; + } + + /** + * Gets the next node. + * + * @return the next node + * @since 3.1 + */ + protected Node getNextNode() { + return next; + } + + /** + * Sets the next node. + * + * @param next the next node + * @since 3.1 + */ + protected void setNextNode(final Node next) { + this.next = next; + } + } + + //----------------------------------------------------------------------- + /** + * A list iterator over the linked list. + */ + protected static class LinkedListIterator implements ListIterator, OrderedIterator { + + /** The parent list */ + protected final AbstractLinkedList parent; + + /** + * The node that will be returned by {@link #next()}. If this is equal + * to {@link AbstractLinkedList#header} then there are no more values to return. + */ + protected Node next; + + /** + * The index of {@link #next}. + */ + protected int nextIndex; + + /** + * The last node that was returned by {@link #next()} or {@link + * #previous()}. Set to null if {@link #next()} or {@link + * #previous()} haven't been called, or if the node has been removed + * with {@link #remove()} or a new node added with {@link #add(Object)}. + * Should be accessed through {@link #getLastNodeReturned()} to enforce + * this behaviour. + */ + protected Node current; + + /** + * The modification count that the list is expected to have. If the list + * doesn't have this count, then a + * {@link java.util.ConcurrentModificationException} may be thrown by + * the operations. + */ + protected int expectedModCount; + + /** + * Create a ListIterator for a list. + * + * @param parent the parent list + * @param fromIndex the index to start at + * @throws IndexOutOfBoundsException if fromIndex is less than 0 or greater than the size of the list + */ + protected LinkedListIterator(final AbstractLinkedList parent, final int fromIndex) + throws IndexOutOfBoundsException { + super(); + this.parent = parent; + this.expectedModCount = parent.modCount; + this.next = parent.getNode(fromIndex, true); + this.nextIndex = fromIndex; + } + + /** + * Checks the modification count of the list is the value that this + * object expects. + * + * @throws ConcurrentModificationException If the list's modification + * count isn't the value that was expected. + */ + protected void checkModCount() { + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + /** + * Gets the last node returned. + * + * @return the last node returned + * @throws IllegalStateException If {@link #next()} or {@link #previous()} haven't been called, + * or if the node has been removed with {@link #remove()} or a new node added with {@link #add(Object)}. + */ + protected Node getLastNodeReturned() throws IllegalStateException { + if (current == null) { + throw new IllegalStateException(); + } + return current; + } + + public boolean hasNext() { + return next != parent.header; + } + + public E next() { + checkModCount(); + if (!hasNext()) { + throw new NoSuchElementException("No element at index " + nextIndex + "."); + } + final E value = next.getValue(); + current = next; + next = next.next; + nextIndex++; + return value; + } + + public boolean hasPrevious() { + return next.previous != parent.header; + } + + public E previous() { + checkModCount(); + if (!hasPrevious()) { + throw new NoSuchElementException("Already at start of list."); + } + next = next.previous; + final E value = next.getValue(); + current = next; + nextIndex--; + return value; + } + + public int nextIndex() { + return nextIndex; + } + + public int previousIndex() { + // not normally overridden, as relative to nextIndex() + return nextIndex() - 1; + } + + public void remove() { + checkModCount(); + if (current == next) { + // remove() following previous() + next = next.next; + parent.removeNode(getLastNodeReturned()); + } else { + // remove() following next() + parent.removeNode(getLastNodeReturned()); + nextIndex--; + } + current = null; + expectedModCount++; + } + + public void set(final E obj) { + checkModCount(); + getLastNodeReturned().setValue(obj); + } + + public void add(final E obj) { + checkModCount(); + parent.addNodeBefore(next, obj); + current = null; + nextIndex++; + expectedModCount++; + } + + } + + //----------------------------------------------------------------------- + /** + * A list iterator over the linked sub list. + */ + protected static class LinkedSubListIterator extends LinkedListIterator { + + /** The parent list */ + protected final LinkedSubList sub; + + protected LinkedSubListIterator(final LinkedSubList sub, final int startIndex) { + super(sub.parent, startIndex + sub.offset); + this.sub = sub; + } + + @Override + public boolean hasNext() { + return nextIndex() < sub.size; + } + + @Override + public boolean hasPrevious() { + return previousIndex() >= 0; + } + + @Override + public int nextIndex() { + return super.nextIndex() - sub.offset; + } + + @Override + public void add(final E obj) { + super.add(obj); + sub.expectedModCount = parent.modCount; + sub.size++; + } + + @Override + public void remove() { + super.remove(); + sub.expectedModCount = parent.modCount; + sub.size--; + } + } + + //----------------------------------------------------------------------- + /** + * The sublist implementation for AbstractLinkedList. + */ + protected static class LinkedSubList extends AbstractList { + /** The main list */ + AbstractLinkedList parent; + /** Offset from the main list */ + int offset; + /** Sublist size */ + int size; + /** Sublist modCount */ + int expectedModCount; + + protected LinkedSubList(final AbstractLinkedList parent, final int fromIndex, final int toIndex) { + if (fromIndex < 0) { + throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); + } + if (toIndex > parent.size()) { + throw new IndexOutOfBoundsException("toIndex = " + toIndex); + } + if (fromIndex > toIndex) { + throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); + } + this.parent = parent; + this.offset = fromIndex; + this.size = toIndex - fromIndex; + this.expectedModCount = parent.modCount; + } + + @Override + public int size() { + checkModCount(); + return size; + } + + @Override + public E get(final int index) { + rangeCheck(index, size); + checkModCount(); + return parent.get(index + offset); + } + + @Override + public void add(final int index, final E obj) { + rangeCheck(index, size + 1); + checkModCount(); + parent.add(index + offset, obj); + expectedModCount = parent.modCount; + size++; + LinkedSubList.this.modCount++; + } + + @Override + public E remove(final int index) { + rangeCheck(index, size); + checkModCount(); + final E result = parent.remove(index + offset); + expectedModCount = parent.modCount; + size--; + LinkedSubList.this.modCount++; + return result; + } + + @Override + public boolean addAll(final Collection coll) { + return addAll(size, coll); + } + + @Override + public boolean addAll(final int index, final Collection coll) { + rangeCheck(index, size + 1); + final int cSize = coll.size(); + if (cSize == 0) { + return false; + } + + checkModCount(); + parent.addAll(offset + index, coll); + expectedModCount = parent.modCount; + size += cSize; + LinkedSubList.this.modCount++; + return true; + } + + @Override + public E set(final int index, final E obj) { + rangeCheck(index, size); + checkModCount(); + return parent.set(index + offset, obj); + } + + @Override + public void clear() { + checkModCount(); + final Iterator it = iterator(); + while (it.hasNext()) { + it.next(); + it.remove(); + } + } + + @Override + public Iterator iterator() { + checkModCount(); + return parent.createSubListIterator(this); + } + + @Override + public ListIterator listIterator(final int index) { + rangeCheck(index, size + 1); + checkModCount(); + return parent.createSubListListIterator(this, index); + } + + @Override + public List subList(final int fromIndexInclusive, final int toIndexExclusive) { + return new LinkedSubList(parent, fromIndexInclusive + offset, toIndexExclusive + offset); + } + + protected void rangeCheck(final int index, final int beyond) { + if (index < 0 || index >= beyond) { + throw new IndexOutOfBoundsException("Index '" + index + "' out of bounds for size '" + size + "'"); + } + } + + protected void checkModCount() { + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractListDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractListDecorator.java new file mode 100644 index 000000000..45738baee --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractListDecorator.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.collections4.collection.AbstractCollectionDecorator; + +/** + * Decorates another {@link List} to provide additional behaviour. + *

              + * Methods are forwarded directly to the decorated list. + * + * @param the type of the elements in the list + * @since 3.0 + * @version $Id: AbstractListDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractListDecorator extends AbstractCollectionDecorator + implements List { + + /** Serialization version--necessary in an abstract class? */ + private static final long serialVersionUID = 4500739654952315623L; + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since 3.1 + */ + protected AbstractListDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param list the list to decorate, must not be null + * @throws NullPointerException if list is null + */ + protected AbstractListDecorator(final List list) { + super(list); + } + + /** + * Gets the list being decorated. + * + * @return the decorated list + */ + @Override + protected List decorated() { + return (List) super.decorated(); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + //----------------------------------------------------------------------- + + public void add(final int index, final E object) { + decorated().add(index, object); + } + + public boolean addAll(final int index, final Collection coll) { + return decorated().addAll(index, coll); + } + + public E get(final int index) { + return decorated().get(index); + } + + public int indexOf(final Object object) { + return decorated().indexOf(object); + } + + public int lastIndexOf(final Object object) { + return decorated().lastIndexOf(object); + } + + public ListIterator listIterator() { + return decorated().listIterator(); + } + + public ListIterator listIterator(final int index) { + return decorated().listIterator(index); + } + + public E remove(final int index) { + return decorated().remove(index); + } + + public E set(final int index, final E object) { + return decorated().set(index, object); + } + + public List subList(final int fromIndex, final int toIndex) { + return decorated().subList(fromIndex, toIndex); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractSerializableListDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractSerializableListDecorator.java new file mode 100644 index 000000000..0b6c539b9 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/AbstractSerializableListDecorator.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.List; + +/** + * Serializable subclass of AbstractListDecorator. + * + * @since 3.1 + * @version $Id: AbstractSerializableListDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractSerializableListDecorator + extends AbstractListDecorator { + + /** Serialization version */ + private static final long serialVersionUID = 2684959196747496299L; + + /** + * Constructor that wraps (not copies). + * + * @param list the list to decorate, must not be null + * @throws NullPointerException if list is null + */ + protected AbstractSerializableListDecorator(final List list) { + super(list); + } + + //----------------------------------------------------------------------- + /** + * Write the list out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the list in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setCollection((Collection) in.readObject()); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/CursorableLinkedList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/CursorableLinkedList.java new file mode 100644 index 000000000..3b21ce40f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/CursorableLinkedList.java @@ -0,0 +1,620 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * A List implementation with a ListIterator that + * allows concurrent modifications to the underlying list. + *

              + * This implementation supports all of the optional {@link List} operations. + * It extends AbstractLinkedList and thus provides the + * stack/queue/dequeue operations available in {@link java.util.LinkedList}. + *

              + * The main feature of this class is the ability to modify the list and the + * iterator at the same time. Both the {@link #listIterator()} and {@link #cursor()} + * methods provides access to a Cursor instance which extends + * ListIterator. The cursor allows changes to the list concurrent + * with changes to the iterator. Note that the {@link #iterator()} method and + * sublists do not provide this cursor behaviour. + *

              + * The Cursor class is provided partly for backwards compatibility + * and partly because it allows the cursor to be directly closed. Closing the + * cursor is optional because references are held via a WeakReference. + * For most purposes, simply modify the iterator and list at will, and then let + * the garbage collector to the rest. + *

              + * Note that this implementation is not synchronized. + * + * @see java.util.LinkedList + * @since 1.0 + * @version $Id: CursorableLinkedList.java 1533984 2013-10-20 21:12:51Z tn $ + */ +public class CursorableLinkedList extends AbstractLinkedList implements Serializable { + + /** Ensure serialization compatibility */ + private static final long serialVersionUID = 8836393098519411393L; + + /** A list of the cursor currently open on this list */ + private transient List>> cursors; + + //----------------------------------------------------------------------- + /** + * Constructor that creates. + */ + public CursorableLinkedList() { + super(); + init(); // must call init() as use super(); + } + + /** + * Constructor that copies the specified collection + * + * @param coll the collection to copy + */ + public CursorableLinkedList(final Collection coll) { + super(coll); + } + + /** + * The equivalent of a default constructor called + * by any constructor and by readObject. + */ + @Override + protected void init() { + super.init(); + cursors = new ArrayList>>(); + } + + //----------------------------------------------------------------------- + /** + * Returns an iterator that does not support concurrent modification. + *

              + * If the underlying list is modified while iterating using this iterator + * a ConcurrentModificationException will occur. + * The cursor behaviour is available via {@link #listIterator()}. + * + * @return a new iterator that does not support concurrent modification + */ + @Override + public Iterator iterator() { + return super.listIterator(0); + } + + /** + * Returns a cursor iterator that allows changes to the underlying list in parallel. + *

              + * The cursor enables iteration and list changes to occur in any order without + * invalidating the iterator (from one thread). When elements are added to the + * list, an event is fired to all active cursors enabling them to adjust to the + * change in the list. + *

              + * When the "current" (i.e., last returned by {@link ListIterator#next} + * or {@link ListIterator#previous}) element of the list is removed, + * the cursor automatically adjusts to the change (invalidating the + * last returned value such that it cannot be removed). + * + * @return a new cursor iterator + */ + @Override + public ListIterator listIterator() { + return cursor(0); + } + + /** + * Returns a cursor iterator that allows changes to the underlying list in parallel. + *

              + * The cursor enables iteration and list changes to occur in any order without + * invalidating the iterator (from one thread). When elements are added to the + * list, an event is fired to all active cursors enabling them to adjust to the + * change in the list. + *

              + * When the "current" (i.e., last returned by {@link ListIterator#next} + * or {@link ListIterator#previous}) element of the list is removed, + * the cursor automatically adjusts to the change (invalidating the + * last returned value such that it cannot be removed). + * + * @param fromIndex the index to start from + * @return a new cursor iterator + */ + @Override + public ListIterator listIterator(final int fromIndex) { + return cursor(fromIndex); + } + + /** + * Returns a {@link Cursor} for iterating through the elements of this list. + *

              + * A Cursor is a ListIterator with an additional + * close() method. Calling this method immediately discards the + * references to the cursor. If it is not called, then the garbage collector + * will still remove the reference as it is held via a WeakReference. + *

              + * The cursor enables iteration and list changes to occur in any order without + * invalidating the iterator (from one thread). When elements are added to the + * list, an event is fired to all active cursors enabling them to adjust to the + * change in the list. + *

              + * When the "current" (i.e., last returned by {@link ListIterator#next} + * or {@link ListIterator#previous}) element of the list is removed, + * the cursor automatically adjusts to the change (invalidating the + * last returned value such that it cannot be removed). + *

              + * The {@link #listIterator()} method returns the same as this method, and can + * be cast to a Cursor if the close method is required. + * + * @return a new cursor iterator + */ + public CursorableLinkedList.Cursor cursor() { + return cursor(0); + } + + /** + * Returns a {@link Cursor} for iterating through the elements of this list + * starting from a specified index. + *

              + * A Cursor is a ListIterator with an additional + * close() method. Calling this method immediately discards the + * references to the cursor. If it is not called, then the garbage collector + * will still remove the reference as it is held via a WeakReference. + *

              + * The cursor enables iteration and list changes to occur in any order without + * invalidating the iterator (from one thread). When elements are added to the + * list, an event is fired to all active cursors enabling them to adjust to the + * change in the list. + *

              + * When the "current" (i.e., last returned by {@link ListIterator#next} + * or {@link ListIterator#previous}) element of the list is removed, + * the cursor automatically adjusts to the change (invalidating the + * last returned value such that it cannot be removed). + *

              + * The {@link #listIterator(int)} method returns the same as this method, and can + * be cast to a Cursor if the close method is required. + * + * @param fromIndex the index to start from + * @return a new cursor iterator + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > size()). + */ + public CursorableLinkedList.Cursor cursor(final int fromIndex) { + final Cursor cursor = new Cursor(this, fromIndex); + registerCursor(cursor); + return cursor; + } + + //----------------------------------------------------------------------- + /** + * Updates the node with a new value. + * This implementation sets the value on the node. + * Subclasses can override this to record the change. + * + * @param node node to update + * @param value new value of the node + */ + @Override + protected void updateNode(final Node node, final E value) { + super.updateNode(node, value); + broadcastNodeChanged(node); + } + + /** + * Inserts a new node into the list. + * + * @param nodeToInsert new node to insert + * @param insertBeforeNode node to insert before + * @throws NullPointerException if either node is null + */ + @Override + protected void addNode(final Node nodeToInsert, final Node insertBeforeNode) { + super.addNode(nodeToInsert, insertBeforeNode); + broadcastNodeInserted(nodeToInsert); + } + + /** + * Removes the specified node from the list. + * + * @param node the node to remove + * @throws NullPointerException if node is null + */ + @Override + protected void removeNode(final Node node) { + super.removeNode(node); + broadcastNodeRemoved(node); + } + + /** + * Removes all nodes by iteration. + */ + @Override + protected void removeAllNodes() { + if (size() > 0) { + // superclass implementation would break all the iterators + final Iterator it = iterator(); + while (it.hasNext()) { + it.next(); + it.remove(); + } + } + } + + //----------------------------------------------------------------------- + /** + * Registers a cursor to be notified of changes to this list. + * + * @param cursor the cursor to register + */ + protected void registerCursor(final Cursor cursor) { + // We take this opportunity to clean the cursors list + // of WeakReference objects to garbage-collected cursors. + for (final Iterator>> it = cursors.iterator(); it.hasNext();) { + final WeakReference> ref = it.next(); + if (ref.get() == null) { + it.remove(); + } + } + cursors.add(new WeakReference>(cursor)); + } + + /** + * Deregisters a cursor from the list to be notified of changes. + * + * @param cursor the cursor to deregister + */ + protected void unregisterCursor(final Cursor cursor) { + for (final Iterator>> it = cursors.iterator(); it.hasNext();) { + final WeakReference> ref = it.next(); + final Cursor cur = ref.get(); + if (cur == null) { + // some other unrelated cursor object has been + // garbage-collected; let's take the opportunity to + // clean up the cursors list anyway.. + it.remove(); + } else if (cur == cursor) { + ref.clear(); + it.remove(); + break; + } + } + } + + //----------------------------------------------------------------------- + /** + * Informs all of my registered cursors that the specified + * element was changed. + * + * @param node the node that was changed + */ + protected void broadcastNodeChanged(final Node node) { + final Iterator>> it = cursors.iterator(); + while (it.hasNext()) { + final WeakReference> ref = it.next(); + final Cursor cursor = ref.get(); + if (cursor == null) { + it.remove(); // clean up list + } else { + cursor.nodeChanged(node); + } + } + } + + /** + * Informs all of my registered cursors that the specified + * element was just removed from my list. + * + * @param node the node that was changed + */ + protected void broadcastNodeRemoved(final Node node) { + final Iterator>> it = cursors.iterator(); + while (it.hasNext()) { + final WeakReference> ref = it.next(); + final Cursor cursor = ref.get(); + if (cursor == null) { + it.remove(); // clean up list + } else { + cursor.nodeRemoved(node); + } + } + } + + /** + * Informs all of my registered cursors that the specified + * element was just added to my list. + * + * @param node the node that was changed + */ + protected void broadcastNodeInserted(final Node node) { + final Iterator>> it = cursors.iterator(); + while (it.hasNext()) { + final WeakReference> ref = it.next(); + final Cursor cursor = ref.get(); + if (cursor == null) { + it.remove(); // clean up list + } else { + cursor.nodeInserted(node); + } + } + } + + //----------------------------------------------------------------------- + /** + * Serializes the data held in this object to the stream specified. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + + /** + * Deserializes the data held in this object to the stream specified. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + doReadObject(in); + } + + //----------------------------------------------------------------------- + /** + * Creates a list iterator for the sublist. + * + * @param subList the sublist to get an iterator for + * @param fromIndex the index to start from, relative to the sublist + * @return the list iterator for the sublist + */ + @Override + protected ListIterator createSubListListIterator(final LinkedSubList subList, final int fromIndex) { + final SubCursor cursor = new SubCursor(subList, fromIndex); + registerCursor(cursor); + return cursor; + } + + //----------------------------------------------------------------------- + /** + * An extended ListIterator that allows concurrent changes to + * the underlying list. + */ + public static class Cursor extends AbstractLinkedList.LinkedListIterator { + /** Is the cursor valid (not closed) */ + boolean valid = true; + /** Is the next index valid */ + boolean nextIndexValid = true; + /** Flag to indicate if the current element was removed by another object. */ + boolean currentRemovedByAnother = false; + + /** + * Constructs a new cursor. + * + * @param parent the parent list + * @param index the index to start from + */ + protected Cursor(final CursorableLinkedList parent, final int index) { + super(parent, index); + valid = true; + } + + /** + * Removes the item last returned by this iterator. + *

              + * There may have been subsequent alterations to the list + * since you obtained this item, however you can still remove it. + * You can even remove it if the item is no longer in the main list. + * However, you can't call this method on the same iterator more + * than once without calling next() or previous(). + * + * @throws IllegalStateException if there is no item to remove + */ + @Override + public void remove() { + // overridden, as the nodeRemoved() method updates the iterator + // state in the parent.removeNode() call below + if (current == null && currentRemovedByAnother) { // NOPMD + // quietly ignore, as the last returned node was removed + // by the list or some other iterator + // by ignoring it, we keep this iterator independent from + // other changes as much as possible + } else { + checkModCount(); + parent.removeNode(getLastNodeReturned()); + } + currentRemovedByAnother = false; + } + + /** + * Adds an object to the list. + * The object added here will be the new 'previous' in the iterator. + * + * @param obj the object to add + */ + @Override + public void add(final E obj) { + // overridden, as the nodeInserted() method updates the iterator state + super.add(obj); + // matches the (next.previous == node) clause in nodeInserted() + // thus next gets changed - reset it again here + next = next.next; + } + + // set is not overridden, as it works ok + // note that we want it to throw an exception if the element being + // set has been removed from the real list (compare this with the + // remove method where we silently ignore this case) + + /** + * Gets the index of the next element to be returned. + * + * @return the next index + */ + @Override + public int nextIndex() { + if (nextIndexValid == false) { + if (next == parent.header) { + nextIndex = parent.size(); + } else { + int pos = 0; + Node temp = parent.header.next; + while (temp != next) { + pos++; + temp = temp.next; + } + nextIndex = pos; + } + nextIndexValid = true; + } + return nextIndex; + } + + /** + * Handle event from the list when a node has changed. + * + * @param node the node that changed + */ + protected void nodeChanged(final Node node) { + // do nothing + } + + /** + * Handle event from the list when a node has been removed. + * + * @param node the node that was removed + */ + protected void nodeRemoved(final Node node) { + if (node == next && node == current) { + // state where next() followed by previous() + next = node.next; + current = null; + currentRemovedByAnother = true; + } else if (node == next) { + // state where next() not followed by previous() + // and we are matching next node + next = node.next; + currentRemovedByAnother = false; + } else if (node == current) { + // state where next() not followed by previous() + // and we are matching current (last returned) node + current = null; + currentRemovedByAnother = true; + nextIndex--; + } else { + nextIndexValid = false; + currentRemovedByAnother = false; + } + } + + /** + * Handle event from the list when a node has been added. + * + * @param node the node that was added + */ + protected void nodeInserted(final Node node) { + if (node.previous == current) { + next = node; + } else if (next.previous == node) { + next = node; + } else { + nextIndexValid = false; + } + } + + /** + * Override superclass modCount check, and replace it with our valid flag. + */ + @Override + protected void checkModCount() { + if (!valid) { + throw new ConcurrentModificationException("Cursor closed"); + } + } + + /** + * Mark this cursor as no longer being needed. Any resources + * associated with this cursor are immediately released. + * In previous versions of this class, it was mandatory to close + * all cursor objects to avoid memory leaks. It is no longer + * necessary to call this close method; an instance of this class + * can now be treated exactly like a normal iterator. + */ + public void close() { + if (valid) { + ((CursorableLinkedList) parent).unregisterCursor(this); + valid = false; + } + } + } + + //----------------------------------------------------------------------- + /** + * A cursor for the sublist based on LinkedSubListIterator. + * + * @since 3.2 + */ + protected static class SubCursor extends Cursor { + + /** The parent list */ + protected final LinkedSubList sub; + + /** + * Constructs a new cursor. + * + * @param sub the sub list + * @param index the index to start from + */ + protected SubCursor(final LinkedSubList sub, final int index) { + super((CursorableLinkedList) sub.parent, index + sub.offset); + this.sub = sub; + } + + @Override + public boolean hasNext() { + return nextIndex() < sub.size; + } + + @Override + public boolean hasPrevious() { + return previousIndex() >= 0; + } + + @Override + public int nextIndex() { + return super.nextIndex() - sub.offset; + } + + @Override + public void add(final E obj) { + super.add(obj); + sub.expectedModCount = parent.modCount; + sub.size++; + } + + @Override + public void remove() { + super.remove(); + sub.expectedModCount = parent.modCount; + sub.size--; + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/FixedSizeList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/FixedSizeList.java new file mode 100644 index 000000000..aa8ec6f70 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/FixedSizeList.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.collections4.BoundedCollection; +import org.apache.commons.collections4.iterators.AbstractListIteratorDecorator; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; + +/** + * Decorates another List to fix the size preventing add/remove. + *

              + * The add, remove, clear and retain operations are unsupported. + * The set method is allowed (as it doesn't change the list size). + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: FixedSizeList.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class FixedSizeList + extends AbstractSerializableListDecorator + implements BoundedCollection { + + /** Serialization version */ + private static final long serialVersionUID = -2218010673611160319L; + + /** + * Factory method to create a fixed size list. + * + * @param the type of the elements in the list + * @param list the list to decorate, must not be null + * @return a new fixed size list + * @throws NullPointerException if list is null + * @since 4.0 + */ + public static FixedSizeList fixedSizeList(final List list) { + return new FixedSizeList(list); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param list the list to decorate, must not be null + * @throws NullPointerException if list is null + */ + protected FixedSizeList(final List list) { + super(list); + } + + //----------------------------------------------------------------------- + @Override + public boolean add(final E object) { + throw new UnsupportedOperationException("List is fixed size"); + } + + @Override + public void add(final int index, final E object) { + throw new UnsupportedOperationException("List is fixed size"); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException("List is fixed size"); + } + + @Override + public boolean addAll(final int index, final Collection coll) { + throw new UnsupportedOperationException("List is fixed size"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("List is fixed size"); + } + + @Override + public E get(final int index) { + return decorated().get(index); + } + + @Override + public int indexOf(final Object object) { + return decorated().indexOf(object); + } + + @Override + public Iterator iterator() { + return UnmodifiableIterator.unmodifiableIterator(decorated().iterator()); + } + + @Override + public int lastIndexOf(final Object object) { + return decorated().lastIndexOf(object); + } + + @Override + public ListIterator listIterator() { + return new FixedSizeListIterator(decorated().listIterator(0)); + } + + @Override + public ListIterator listIterator(final int index) { + return new FixedSizeListIterator(decorated().listIterator(index)); + } + + @Override + public E remove(final int index) { + throw new UnsupportedOperationException("List is fixed size"); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException("List is fixed size"); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException("List is fixed size"); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException("List is fixed size"); + } + + @Override + public E set(final int index, final E object) { + return decorated().set(index, object); + } + + @Override + public List subList(final int fromIndex, final int toIndex) { + final List sub = decorated().subList(fromIndex, toIndex); + return new FixedSizeList(sub); + } + + /** + * List iterator that only permits changes via set() + */ + private class FixedSizeListIterator extends AbstractListIteratorDecorator { + protected FixedSizeListIterator(final ListIterator iterator) { + super(iterator); + } + @Override + public void remove() { + throw new UnsupportedOperationException("List is fixed size"); + } + @Override + public void add(final Object object) { + throw new UnsupportedOperationException("List is fixed size"); + } + } + + public boolean isFull() { + return true; + } + + public int maxSize() { + return size(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/GrowthList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/GrowthList.java new file mode 100644 index 000000000..92c22819e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/GrowthList.java @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Decorates another List to make it seamlessly grow when + * indices larger than the list size are used on add and set, + * avoiding most IndexOutOfBoundsExceptions. + *

              + * This class avoids errors by growing when a set or add method would + * normally throw an IndexOutOfBoundsException. + * Note that IndexOutOfBoundsException IS returned for invalid negative indices. + *

              + * Trying to set or add to an index larger than the size will cause the list + * to grow (using null elements). Clearly, care must be taken + * not to use excessively large indices, as the internal list will grow to + * match. + *

              + * Trying to use any method other than add or set with an invalid index will + * call the underlying list and probably result in an IndexOutOfBoundsException. + *

              + * Take care when using this list with null values, as + * null is the value added when growing the list. + *

              + * All sub-lists will access the underlying list directly, and will throw + * IndexOutOfBoundsExceptions. + *

              + * This class differs from {@link LazyList} because here growth occurs on + * set and add, where LazyList grows on get. However, they + * can be used together by decorating twice. + * + * @see LazyList + * @since 3.2 + * @version $Id: GrowthList.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class GrowthList extends AbstractSerializableListDecorator { + + /** Serialization version */ + private static final long serialVersionUID = -3620001881672L; + + /** + * Factory method to create a growth list. + * + * @param the type of the elements in the list + * @param list the list to decorate, must not be null + * @return a new growth list + * @throws NullPointerException if list is null + * @since 4.0 + */ + public static GrowthList growthList(final List list) { + return new GrowthList(list); + } + + //----------------------------------------------------------------------- + /** + * Constructor that uses an ArrayList internally. + */ + public GrowthList() { + super(new ArrayList()); + } + + /** + * Constructor that uses an ArrayList internally. + * + * @param initialSize the initial size of the ArrayList + * @throws IllegalArgumentException if initial size is invalid + */ + public GrowthList(final int initialSize) { + super(new ArrayList(initialSize)); + } + + /** + * Constructor that wraps (not copies). + * + * @param list the list to decorate, must not be null + * @throws NullPointerException if list is null + */ + protected GrowthList(final List list) { + super(list); + } + + //----------------------------------------------------------------------- + /** + * Decorate the add method to perform the growth behaviour. + *

              + * If the requested index is greater than the current size, the list will + * grow to the new size. Indices between the old size and the requested + * size will be filled with null. + *

              + * If the index is less than the current size, the value will be added to + * the underlying list directly. + * If the index is less than zero, the underlying list is called, which + * will probably throw an IndexOutOfBoundsException. + * + * @param index the index to add at + * @param element the object to add at the specified index + * @throws UnsupportedOperationException if the underlying list doesn't implement set + * @throws ClassCastException if the underlying list rejects the element + * @throws IllegalArgumentException if the underlying list rejects the element + */ + @Override + public void add(final int index, final E element) { + final int size = decorated().size(); + if (index > size) { + decorated().addAll(Collections.nCopies(index - size, null)); + } + decorated().add(index, element); + } + + //----------------------------------------------------------------------- + /** + * Decorate the addAll method to perform the growth behaviour. + *

              + * If the requested index is greater than the current size, the list will + * grow to the new size. Indices between the old size and the requested + * size will be filled with null. + *

              + * If the index is less than the current size, the values will be added to + * the underlying list directly. + * If the index is less than zero, the underlying list is called, which + * will probably throw an IndexOutOfBoundsException. + * + * @param index the index to add at + * @param coll the collection to add at the specified index + * @return true if the list changed + * @throws UnsupportedOperationException if the underlying list doesn't implement set + * @throws ClassCastException if the underlying list rejects the element + * @throws IllegalArgumentException if the underlying list rejects the element + */ + @Override + public boolean addAll(final int index, final Collection coll) { + final int size = decorated().size(); + boolean result = false; + if (index > size) { + decorated().addAll(Collections.nCopies(index - size, null)); + result = true; + } + return decorated().addAll(index, coll) | result; + } + + //----------------------------------------------------------------------- + /** + * Decorate the set method to perform the growth behaviour. + *

              + * If the requested index is greater than the current size, the list will + * grow to the new size. Indices between the old size and the requested + * size will be filled with null. + *

              + * If the index is less than the current size, the value will be set onto + * the underlying list directly. + * If the index is less than zero, the underlying list is called, which + * will probably throw an IndexOutOfBoundsException. + * + * @param index the index to set + * @param element the object to set at the specified index + * @return the object previously at that index + * @throws UnsupportedOperationException if the underlying list doesn't implement set + * @throws ClassCastException if the underlying list rejects the element + * @throws IllegalArgumentException if the underlying list rejects the element + */ + @Override + public E set(final int index, final E element) { + final int size = decorated().size(); + if (index >= size) { + decorated().addAll(Collections.nCopies(index - size + 1, null)); + } + return decorated().set(index, element); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/LazyList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/LazyList.java new file mode 100644 index 000000000..c932209a5 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/LazyList.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.List; + +import org.apache.commons.collections4.Factory; + +/** + * Decorates another List to create objects in the list on demand. + *

              + * When the {@link #get(int)} method is called with an index greater than + * the size of the list, the list will automatically grow in size and return + * a new object from the specified factory. The gaps will be filled by null. + * If a get method call encounters a null, it will be replaced with a new + * object from the factory. Thus this list is unsuitable for storing null + * objects. + *

              + * For instance: + * + *

              + * Factory<Date> factory = new Factory<Date>() {
              + *     public Date create() {
              + *         return new Date();
              + *     }
              + * }
              + * List<Date> lazy = LazyList.decorate(new ArrayList<Date>(), factory);
              + * Date date = lazy.get(3);
              + * 
              + * + * After the above code is executed, date will contain + * a new Date instance. Furthermore, that Date + * instance is the fourth element in the list. The first, second, + * and third element are all set to null. + *

              + * This class differs from {@link GrowthList} because here growth occurs on + * get, where GrowthList grows on set and add. However, they + * could easily be used together by decorating twice. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @see GrowthList + * @since 3.0 + * @version $Id: LazyList.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class LazyList extends AbstractSerializableListDecorator { + + /** Serialization version */ + private static final long serialVersionUID = -1708388017160694542L; + + /** The factory to use to lazily instantiate the objects */ + private final Factory factory; + + /** + * Factory method to create a lazily instantiating list. + * + * @param the type of the elements in the list + * @param list the list to decorate, must not be null + * @param factory the factory to use for creation, must not be null + * @return a new lazy list + * @throws NullPointerException if list or factory is null + * @since 4.0 + */ + public static LazyList lazyList(final List list, final Factory factory) { + return new LazyList(list, factory); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param list the list to decorate, must not be null + * @param factory the factory to use for creation, must not be null + * @throws NullPointerException if list or factory is null + */ + protected LazyList(final List list, final Factory factory) { + super(list); + if (factory == null) { + throw new IllegalArgumentException("Factory must not be null"); + } + this.factory = factory; + } + + //----------------------------------------------------------------------- + /** + * Decorate the get method to perform the lazy behaviour. + *

              + * If the requested index is greater than the current size, the list will + * grow to the new size and a new object will be returned from the factory. + * Indexes in-between the old size and the requested size are left with a + * placeholder that is replaced with a factory object when requested. + * + * @param index the index to retrieve + * @return the element at the given index + */ + @Override + public E get(final int index) { + final int size = decorated().size(); + if (index < size) { + // within bounds, get the object + E object = decorated().get(index); + if (object == null) { + // item is a place holder, create new one, set and return + object = factory.create(); + decorated().set(index, object); + return object; + } + // good and ready to go + return object; + } + // we have to grow the list + for (int i = size; i < index; i++) { + decorated().add(null); + } + // create our last object, set and return + final E object = factory.create(); + decorated().add(object); + return object; + } + + @Override + public List subList(final int fromIndex, final int toIndex) { + final List sub = decorated().subList(fromIndex, toIndex); + return new LazyList(sub, factory); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/NodeCachingLinkedList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/NodeCachingLinkedList.java new file mode 100644 index 000000000..f7b84b154 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/NodeCachingLinkedList.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; + +/** + * A List implementation that stores a cache of internal Node objects + * in an effort to reduce wasteful object creation. + *

              + * A linked list creates one Node for each item of data added. This can result in + * a lot of object creation and garbage collection. This implementation seeks to + * avoid that by maintaining a store of cached nodes. + *

              + * This implementation is suitable for long-lived lists where both add and remove + * are used. Short-lived lists, or lists which only grow will have worse performance + * using this class. + *

              + * Note that this implementation is not synchronized. + * + * @since 3.0 + * @version $Id: NodeCachingLinkedList.java 1533984 2013-10-20 21:12:51Z tn $ + */ +public class NodeCachingLinkedList extends AbstractLinkedList implements Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 6897789178562232073L; + + /** + * The default value for {@link #maximumCacheSize}. + */ + private static final int DEFAULT_MAXIMUM_CACHE_SIZE = 20; + + /** + * The first cached node, or null if no nodes are cached. + * Cached nodes are stored in a singly-linked list with + * next pointing to the next element. + */ + private transient Node firstCachedNode; + + /** + * The size of the cache. + */ + private transient int cacheSize; + + /** + * The maximum size of the cache. + */ + private int maximumCacheSize; + + //----------------------------------------------------------------------- + /** + * Constructor that creates. + */ + public NodeCachingLinkedList() { + this(DEFAULT_MAXIMUM_CACHE_SIZE); + } + + /** + * Constructor that copies the specified collection + * + * @param coll the collection to copy + */ + public NodeCachingLinkedList(final Collection coll) { + super(coll); + this.maximumCacheSize = DEFAULT_MAXIMUM_CACHE_SIZE; + } + + /** + * Constructor that species the maximum cache size. + * + * @param maximumCacheSize the maximum cache size + */ + public NodeCachingLinkedList(final int maximumCacheSize) { + super(); + this.maximumCacheSize = maximumCacheSize; + init(); // must call init() as use super(); + } + + //----------------------------------------------------------------------- + /** + * Gets the maximum size of the cache. + * + * @return the maximum cache size + */ + protected int getMaximumCacheSize() { + return maximumCacheSize; + } + + /** + * Sets the maximum size of the cache. + * + * @param maximumCacheSize the new maximum cache size + */ + protected void setMaximumCacheSize(final int maximumCacheSize) { + this.maximumCacheSize = maximumCacheSize; + shrinkCacheToMaximumSize(); + } + + /** + * Reduce the size of the cache to the maximum, if necessary. + */ + protected void shrinkCacheToMaximumSize() { + // Rich Dougherty: This could be more efficient. + while (cacheSize > maximumCacheSize) { + getNodeFromCache(); + } + } + + /** + * Gets a node from the cache. If a node is returned, then the value of + * {@link #cacheSize} is decreased accordingly. The node that is returned + * will have null values for next, previous and element. + * + * @return a node, or null if there are no nodes in the cache. + */ + protected Node getNodeFromCache() { + if (cacheSize == 0) { + return null; + } + final Node cachedNode = firstCachedNode; + firstCachedNode = cachedNode.next; + cachedNode.next = null; // This should be changed anyway, but defensively + // set it to null. + cacheSize--; + return cachedNode; + } + + /** + * Checks whether the cache is full. + * + * @return true if the cache is full + */ + protected boolean isCacheFull() { + return cacheSize >= maximumCacheSize; + } + + /** + * Adds a node to the cache, if the cache isn't full. + * The node's contents are cleared to so they can be garbage collected. + * + * @param node the node to add to the cache + */ + protected void addNodeToCache(final Node node) { + if (isCacheFull()) { + // don't cache the node. + return; + } + // clear the node's contents and add it to the cache. + final Node nextCachedNode = firstCachedNode; + node.previous = null; + node.next = nextCachedNode; + node.setValue(null); + firstCachedNode = node; + cacheSize++; + } + + //----------------------------------------------------------------------- + /** + * Creates a new node, either by reusing one from the cache or creating + * a new one. + * + * @param value value of the new node + * @return the newly created node + */ + @Override + protected Node createNode(final E value) { + final Node cachedNode = getNodeFromCache(); + if (cachedNode == null) { + return super.createNode(value); + } + cachedNode.setValue(value); + return cachedNode; + } + + /** + * Removes the node from the list, storing it in the cache for reuse + * if the cache is not yet full. + * + * @param node the node to remove + */ + @Override + protected void removeNode(final Node node) { + super.removeNode(node); + addNodeToCache(node); + } + + /** + * Removes all the nodes from the list, storing as many as required in the + * cache for reuse. + * + */ + @Override + protected void removeAllNodes() { + // Add the removed nodes to the cache, then remove the rest. + // We can add them to the cache before removing them, since + // {@link AbstractLinkedList.removeAllNodes()} removes the + // nodes by removing references directly from {@link #header}. + final int numberOfNodesToCache = Math.min(size, maximumCacheSize - cacheSize); + Node node = header.next; + for (int currentIndex = 0; currentIndex < numberOfNodesToCache; currentIndex++) { + final Node oldNode = node; + node = node.next; + addNodeToCache(oldNode); + } + super.removeAllNodes(); + } + + //----------------------------------------------------------------------- + /** + * Serializes the data held in this object to the stream specified. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + + /** + * Deserializes the data held in this object to the stream specified. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + doReadObject(in); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/PredicatedList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/PredicatedList.java new file mode 100644 index 000000000..22d21ddc7 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/PredicatedList.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.collection.PredicatedCollection; +import org.apache.commons.collections4.iterators.AbstractListIteratorDecorator; + +/** + * Decorates another List to validate that all additions + * match a specified predicate. + *

              + * This list exists to provide validation for the decorated list. + * It is normally created to decorate an empty list. + * If an object cannot be added to the list, an IllegalArgumentException is thrown. + *

              + * One usage would be to ensure that no null entries are added to the list. + *

              + * {@code
              + * List list =
              + *   PredicatedList.predicatedList(new ArrayList(), PredicateUtils.notNullPredicate());
              + * }
              + * 
              + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: PredicatedList.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PredicatedList extends PredicatedCollection implements List { + + /** Serialization version */ + private static final long serialVersionUID = -5722039223898659102L; + + /** + * Factory method to create a predicated (validating) list. + *

              + * If there are any elements already in the list being decorated, they + * are validated. + * + * @param the type of the elements in the list + * @param list the list to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @return a new predicated list + * @throws NullPointerException if list or predicate is null + * @throws IllegalArgumentException if the list contains invalid elements + * @since 4.0 + */ + public static PredicatedList predicatedList(final List list, final Predicate predicate) { + return new PredicatedList(list, predicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the list being decorated, they + * are validated. + * + * @param list the list to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @throws NullPointerException if list or predicate is null + * @throws IllegalArgumentException if the list contains invalid elements + */ + protected PredicatedList(final List list, final Predicate predicate) { + super(list, predicate); + } + + /** + * Gets the list being decorated. + * + * @return the decorated list + */ + @Override + protected List decorated() { + return (List) super.decorated(); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + //----------------------------------------------------------------------- + + public E get(final int index) { + return decorated().get(index); + } + + public int indexOf(final Object object) { + return decorated().indexOf(object); + } + + public int lastIndexOf(final Object object) { + return decorated().lastIndexOf(object); + } + + public E remove(final int index) { + return decorated().remove(index); + } + + //----------------------------------------------------------------------- + + public void add(final int index, final E object) { + validate(object); + decorated().add(index, object); + } + + public boolean addAll(final int index, final Collection coll) { + for (final E aColl : coll) { + validate(aColl); + } + return decorated().addAll(index, coll); + } + + public ListIterator listIterator() { + return listIterator(0); + } + + public ListIterator listIterator(final int i) { + return new PredicatedListIterator(decorated().listIterator(i)); + } + + public E set(final int index, final E object) { + validate(object); + return decorated().set(index, object); + } + + public List subList(final int fromIndex, final int toIndex) { + final List sub = decorated().subList(fromIndex, toIndex); + return new PredicatedList(sub, predicate); + } + + /** + * Inner class Iterator for the PredicatedList + */ + protected class PredicatedListIterator extends AbstractListIteratorDecorator { + + /** + * Create a new predicated list iterator. + * + * @param iterator the list iterator to decorate + */ + protected PredicatedListIterator(final ListIterator iterator) { + super(iterator); + } + + @Override + public void add(final E object) { + validate(object); + getListIterator().add(object); + } + + @Override + public void set(final E object) { + validate(object); + getListIterator().set(object); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/SetUniqueList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/SetUniqueList.java new file mode 100644 index 000000000..63da3ea35 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/SetUniqueList.java @@ -0,0 +1,424 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; +import org.apache.commons.collections4.iterators.AbstractListIteratorDecorator; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates a List to ensure that no duplicates are present much + * like a Set. + *

              + * The List interface makes certain assumptions/requirements. This + * implementation breaks these in certain ways, but this is merely the result of + * rejecting duplicates. Each violation is explained in the method, but it + * should not affect you. Bear in mind that Sets require immutable objects to + * function correctly. + *

              + * The {@link org.apache.commons.collections4.set.ListOrderedSet ListOrderedSet} + * class provides an alternative approach, by wrapping an existing Set and + * retaining insertion order in the iterator. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: SetUniqueList.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class SetUniqueList extends AbstractSerializableListDecorator { + + /** Serialization version. */ + private static final long serialVersionUID = 7196982186153478694L; + + /** Internal Set to maintain uniqueness. */ + private final Set set; + + /** + * Factory method to create a SetList using the supplied list to retain order. + *

              + * If the list contains duplicates, these are removed (first indexed one + * kept). A HashSet is used for the set behaviour. + * + * @param the element type + * @param list the list to decorate, must not be null + * @return a new {@link SetUniqueList} + * @throws NullPointerException if list is null + * @since 4.0 + */ + public static SetUniqueList setUniqueList(final List list) { + if (list == null) { + throw new NullPointerException("List must not be null"); + } + if (list.isEmpty()) { + return new SetUniqueList(list, new HashSet()); + } + final List temp = new ArrayList(list); + list.clear(); + final SetUniqueList sl = new SetUniqueList(list, new HashSet()); + sl.addAll(temp); + return sl; + } + + // ----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies) the List and specifies the set to use. + *

              + * The set and list must both be correctly initialised to the same elements. + * + * @param set the set to decorate, must not be null + * @param list the list to decorate, must not be null + * @throws NullPointerException if set or list is null + */ + protected SetUniqueList(final List list, final Set set) { + super(list); + if (set == null) { + throw new NullPointerException("Set must not be null"); + } + this.set = set; + } + + // ----------------------------------------------------------------------- + /** + * Gets an unmodifiable view as a Set. + * + * @return an unmodifiable set view + */ + public Set asSet() { + return UnmodifiableSet.unmodifiableSet(set); + } + + // ----------------------------------------------------------------------- + /** + * Adds an element to the list if it is not already present. + *

              + * (Violation) The List interface requires that this + * method returns true always. However this class may return + * false because of the Set behaviour. + * + * @param object the object to add + * @return true if object was added + */ + @Override + public boolean add(final E object) { + // gets initial size + final int sizeBefore = size(); + + // adds element if unique + add(size(), object); + + // compares sizes to detect if collection changed + return sizeBefore != size(); + } + + /** + * Adds an element to a specific index in the list if it is not already + * present. + *

              + * (Violation) The List interface makes the assumption + * that the element is always inserted. This may not happen with this + * implementation. + * + * @param index the index to insert at + * @param object the object to add + */ + @Override + public void add(final int index, final E object) { + // adds element if it is not contained already + if (set.contains(object) == false) { + super.add(index, object); + set.add(object); + } + } + + /** + * Adds a collection of objects to the end of the list avoiding duplicates. + *

              + * Only elements that are not already in this list will be added, and + * duplicates from the specified collection will be ignored. + *

              + * (Violation) The List interface makes the assumption + * that the elements are always inserted. This may not happen with this + * implementation. + * + * @param coll the collection to add in iterator order + * @return true if this collection changed + */ + @Override + public boolean addAll(final Collection coll) { + return addAll(size(), coll); + } + + /** + * Adds a collection of objects a specific index in the list avoiding + * duplicates. + *

              + * Only elements that are not already in this list will be added, and + * duplicates from the specified collection will be ignored. + *

              + * (Violation) The List interface makes the assumption + * that the elements are always inserted. This may not happen with this + * implementation. + * + * @param index the index to insert at + * @param coll the collection to add in iterator order + * @return true if this collection changed + */ + @Override + public boolean addAll(final int index, final Collection coll) { + final List temp = new ArrayList(); + for (final E e : coll) { + if (set.add(e)) { + temp.add(e); + } + } + return super.addAll(index, temp); + } + + // ----------------------------------------------------------------------- + /** + * Sets the value at the specified index avoiding duplicates. + *

              + * The object is set into the specified index. Afterwards, any previous + * duplicate is removed. If the object is not already in the list then a + * normal set occurs. If it is present, then the old version is removed. + * + * @param index the index to insert at + * @param object the object to set + * @return the previous object + */ + @Override + public E set(final int index, final E object) { + final int pos = indexOf(object); + final E removed = super.set(index, object); + + if (pos != -1 && pos != index) { + // the object is already in the unique list + // (and it hasn't been swapped with itself) + super.remove(pos); // remove the duplicate by index + } + + set.remove(removed); // remove the item deleted by the set + set.add(object); // add the new item to the unique set + + return removed; // return the item deleted by the set + } + + @Override + public boolean remove(final Object object) { + final boolean result = set.remove(object); + if (result) { + super.remove(object); + } + return result; + } + + @Override + public E remove(final int index) { + final E result = super.remove(index); + set.remove(result); + return result; + } + + @Override + public boolean removeAll(final Collection coll) { + boolean result = false; + for (final Object name : coll) { + result |= remove(name); + } + return result; + } + + /** + * {@inheritDoc} + *

              + * This implementation iterates over the elements of this list, checking + * each element in turn to see if it's contained in coll. + * If it's not contained, it's removed from this list. As a consequence, + * it is advised to use a collection type for coll that provides + * a fast (e.g. O(1)) implementation of {@link Collection#contains(Object)}. + */ + @Override + public boolean retainAll(final Collection coll) { + boolean result = set.retainAll(coll); + if (result == false) { + return false; + } + if (set.size() == 0) { + super.clear(); + } else { + // use the set as parameter for the call to retainAll to improve performance + super.retainAll(set); + } + return result; + } + + @Override + public void clear() { + super.clear(); + set.clear(); + } + + @Override + public boolean contains(final Object object) { + return set.contains(object); + } + + @Override + public boolean containsAll(final Collection coll) { + return set.containsAll(coll); + } + + @Override + public Iterator iterator() { + return new SetListIterator(super.iterator(), set); + } + + @Override + public ListIterator listIterator() { + return new SetListListIterator(super.listIterator(), set); + } + + @Override + public ListIterator listIterator(final int index) { + return new SetListListIterator(super.listIterator(index), set); + } + + /** + * {@inheritDoc} + *

              + * NOTE: from 4.0, an unmodifiable list will be returned, as changes to the + * subList can invalidate the parent list. + */ + @Override + public List subList(final int fromIndex, final int toIndex) { + final List superSubList = super.subList(fromIndex, toIndex); + final Set subSet = createSetBasedOnList(set, superSubList); + return ListUtils.unmodifiableList(new SetUniqueList(superSubList, subSet)); + } + + /** + * Create a new {@link Set} with the same type as the provided {@code set} + * and populate it with all elements of {@code list}. + * + * @param set the {@link Set} to be used as return type, must not be null + * @param list the {@link List} to populate the {@link Set} + * @return a new {@link Set} populated with all elements of the provided + * {@link List} + */ + @SuppressWarnings("unchecked") + protected Set createSetBasedOnList(final Set set, final List list) { + Set subSet; + if (set.getClass().equals(HashSet.class)) { + subSet = new HashSet(list.size()); + } else { + try { + subSet = set.getClass().newInstance(); + } catch (final InstantiationException ie) { + subSet = new HashSet(); + } catch (final IllegalAccessException iae) { + subSet = new HashSet(); + } + } + subSet.addAll(list); + return subSet; + } + + // ----------------------------------------------------------------------- + /** + * Inner class iterator. + */ + static class SetListIterator extends AbstractIteratorDecorator { + + private final Set set; + private E last = null; + + protected SetListIterator(final Iterator it, final Set set) { + super(it); + this.set = set; + } + + @Override + public E next() { + last = super.next(); + return last; + } + + @Override + public void remove() { + super.remove(); + set.remove(last); + last = null; + } + } + + /** + * Inner class iterator. + */ + static class SetListListIterator extends + AbstractListIteratorDecorator { + + private final Set set; + private E last = null; + + protected SetListListIterator(final ListIterator it, final Set set) { + super(it); + this.set = set; + } + + @Override + public E next() { + last = super.next(); + return last; + } + + @Override + public E previous() { + last = super.previous(); + return last; + } + + @Override + public void remove() { + super.remove(); + set.remove(last); + last = null; + } + + @Override + public void add(final E object) { + if (set.contains(object) == false) { + super.add(object); + set.add(object); + } + } + + @Override + public void set(final E object) { + throw new UnsupportedOperationException("ListIterator does not support set"); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/TransformedList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/TransformedList.java new file mode 100644 index 000000000..c52793e6b --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/TransformedList.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.collection.TransformedCollection; +import org.apache.commons.collections4.iterators.AbstractListIteratorDecorator; + +/** + * Decorates another List to transform objects that are added. + *

              + * The add and set methods are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must + * use the Integer form to remove objects. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: TransformedList.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TransformedList extends TransformedCollection implements List { + + /** Serialization version */ + private static final long serialVersionUID = 1077193035000013141L; + + /** + * Factory method to create a transforming list. + *

              + * If there are any elements already in the list being decorated, they + * are NOT transformed. + * Contrast this with {@link #transformedList(List, Transformer)}. + * + * @param the type of the elements in the list + * @param list the list to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed list + * @throws NullPointerException if list or transformer is null + * @since 4.0 + */ + public static TransformedList transformingList(final List list, + final Transformer transformer) { + return new TransformedList(list, transformer); + } + + /** + * Factory method to create a transforming list that will transform + * existing contents of the specified list. + *

              + * If there are any elements already in the list being decorated, they + * will be transformed by this method. + * Contrast this with {@link #transformingList(List, Transformer)}. + * + * @param the type of the elements in the list + * @param list the list to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed List + * @throws NullPointerException if list or transformer is null + * @since 4.0 + */ + public static TransformedList transformedList(final List list, + final Transformer transformer) { + final TransformedList decorated = new TransformedList(list, transformer); + if (list.size() > 0) { + @SuppressWarnings("unchecked") // list is of type E + final E[] values = (E[]) list.toArray(); // NOPMD - false positive for generics + list.clear(); + for (final E value : values) { + decorated.decorated().add(transformer.transform(value)); + } + } + return decorated; + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the list being decorated, they + * are NOT transformed. + * + * @param list the list to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @throws NullPointerException if list or transformer is null + */ + protected TransformedList(final List list, final Transformer transformer) { + super(list, transformer); + } + + /** + * Gets the decorated list. + * + * @return the decorated list + */ + protected List getList() { + return (List) decorated(); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + //----------------------------------------------------------------------- + + public E get(final int index) { + return getList().get(index); + } + + public int indexOf(final Object object) { + return getList().indexOf(object); + } + + public int lastIndexOf(final Object object) { + return getList().lastIndexOf(object); + } + + public E remove(final int index) { + return getList().remove(index); + } + + //----------------------------------------------------------------------- + + public void add(final int index, E object) { + object = transform(object); + getList().add(index, object); + } + + public boolean addAll(final int index, Collection coll) { + coll = transform(coll); + return getList().addAll(index, coll); + } + + public ListIterator listIterator() { + return listIterator(0); + } + + public ListIterator listIterator(final int i) { + return new TransformedListIterator(getList().listIterator(i)); + } + + public E set(final int index, E object) { + object = transform(object); + return getList().set(index, object); + } + + public List subList(final int fromIndex, final int toIndex) { + final List sub = getList().subList(fromIndex, toIndex); + return new TransformedList(sub, transformer); + } + + /** + * Inner class Iterator for the TransformedList + */ + protected class TransformedListIterator extends AbstractListIteratorDecorator { + + /** + * Create a new transformed list iterator. + * + * @param iterator the list iterator to decorate + */ + protected TransformedListIterator(final ListIterator iterator) { + super(iterator); + } + + @Override + public void add(E object) { + object = transform(object); + getListIterator().add(object); + } + + @Override + public void set(E object) { + object = transform(object); + getListIterator().set(object); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/TreeList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/TreeList.java new file mode 100644 index 000000000..5300d144b --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/TreeList.java @@ -0,0 +1,1120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.AbstractList; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Deque; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.OrderedIterator; + +/** + * A List implementation that is optimised for fast insertions and + * removals at any index in the list. + *

              + * This list implementation utilises a tree structure internally to ensure that + * all insertions and removals are O(log n). This provides much faster performance + * than both an ArrayList and a LinkedList where elements + * are inserted and removed repeatedly from anywhere in the list. + *

              + * The following relative performance statistics are indicative of this class: + *

              + *              get  add  insert  iterate  remove
              + * TreeList       3    5       1       2       1
              + * ArrayList      1    1      40       1      40
              + * LinkedList  5800    1     350       2     325
              + * 
              + * ArrayList is a good general purpose list implementation. + * It is faster than TreeList for most operations except inserting + * and removing in the middle of the list. ArrayList also uses less + * memory as TreeList uses one object per entry. + *

              + * LinkedList is rarely a good choice of implementation. + * TreeList is almost always a good replacement for it, although it + * does use slightly more memory. + * + * @since 3.1 + * @version $Id: TreeList.java 1681434 2015-05-24 10:49:58Z tn $ + */ +public class TreeList extends AbstractList { +// add; toArray; iterator; insert; get; indexOf; remove +// TreeList = 1260;7360;3080; 160; 170;3400; 170; +// ArrayList = 220;1480;1760; 6870; 50;1540; 7200; +// LinkedList = 270;7360;3350;55860;290720;2910;55200; + + /** The root node in the AVL tree */ + private AVLNode root; + + /** The current size of the list */ + private int size; + + //----------------------------------------------------------------------- + /** + * Constructs a new empty list. + */ + public TreeList() { + super(); + } + + /** + * Constructs a new empty list that copies the specified collection. + * + * @param coll the collection to copy + * @throws NullPointerException if the collection is null + */ + public TreeList(final Collection coll) { + super(); + if (!coll.isEmpty()) { + root = new AVLNode(coll); + size = coll.size(); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the element at the specified index. + * + * @param index the index to retrieve + * @return the element at the specified index + */ + @Override + public E get(final int index) { + checkInterval(index, 0, size() - 1); + return root.get(index).getValue(); + } + + /** + * Gets the current size of the list. + * + * @return the current size + */ + @Override + public int size() { + return size; + } + + /** + * Gets an iterator over the list. + * + * @return an iterator over the list + */ + @Override + public Iterator iterator() { + // override to go 75% faster + return listIterator(0); + } + + /** + * Gets a ListIterator over the list. + * + * @return the new iterator + */ + @Override + public ListIterator listIterator() { + // override to go 75% faster + return listIterator(0); + } + + /** + * Gets a ListIterator over the list. + * + * @param fromIndex the index to start from + * @return the new iterator + */ + @Override + public ListIterator listIterator(final int fromIndex) { + // override to go 75% faster + // cannot use EmptyIterator as iterator.add() must work + checkInterval(fromIndex, 0, size()); + return new TreeListIterator(this, fromIndex); + } + + /** + * Searches for the index of an object in the list. + * + * @param object the object to search + * @return the index of the object, -1 if not found + */ + @Override + public int indexOf(final Object object) { + // override to go 75% faster + if (root == null) { + return -1; + } + return root.indexOf(object, root.relativePosition); + } + + /** + * Searches for the presence of an object in the list. + * + * @param object the object to check + * @return true if the object is found + */ + @Override + public boolean contains(final Object object) { + return indexOf(object) >= 0; + } + + /** + * Converts the list into an array. + * + * @return the list as an array + */ + @Override + public Object[] toArray() { + // override to go 20% faster + final Object[] array = new Object[size()]; + if (root != null) { + root.toArray(array, root.relativePosition); + } + return array; + } + + //----------------------------------------------------------------------- + /** + * Adds a new element to the list. + * + * @param index the index to add before + * @param obj the element to add + */ + @Override + public void add(final int index, final E obj) { + modCount++; + checkInterval(index, 0, size()); + if (root == null) { + root = new AVLNode(index, obj, null, null); + } else { + root = root.insert(index, obj); + } + size++; + } + + /** + * Appends all of the elements in the specified collection to the end of this list, + * in the order that they are returned by the specified collection's Iterator. + *

              + * This method runs in O(n + log m) time, where m is + * the size of this list and n is the size of {@code c}. + * + * @param c the collection to be added to this list + * @return {@code true} if this list changed as a result of the call + * @throws NullPointerException {@inheritDoc} + */ + @Override + public boolean addAll(final Collection c) { + if (c.isEmpty()) { + return false; + } + modCount += c.size(); + final AVLNode cTree = new AVLNode(c); + root = root == null ? cTree : root.addAll(cTree, size); + size += c.size(); + return true; + } + + /** + * Sets the element at the specified index. + * + * @param index the index to set + * @param obj the object to store at the specified index + * @return the previous object at that index + * @throws IndexOutOfBoundsException if the index is invalid + */ + @Override + public E set(final int index, final E obj) { + checkInterval(index, 0, size() - 1); + final AVLNode node = root.get(index); + final E result = node.value; + node.setValue(obj); + return result; + } + + /** + * Removes the element at the specified index. + * + * @param index the index to remove + * @return the previous object at that index + */ + @Override + public E remove(final int index) { + modCount++; + checkInterval(index, 0, size() - 1); + final E result = get(index); + root = root.remove(index); + size--; + return result; + } + + /** + * Clears the list, removing all entries. + */ + @Override + public void clear() { + modCount++; + root = null; + size = 0; + } + + //----------------------------------------------------------------------- + /** + * Checks whether the index is valid. + * + * @param index the index to check + * @param startIndex the first allowed index + * @param endIndex the last allowed index + * @throws IndexOutOfBoundsException if the index is invalid + */ + private void checkInterval(final int index, final int startIndex, final int endIndex) { + if (index < startIndex || index > endIndex) { + throw new IndexOutOfBoundsException("Invalid index:" + index + ", size=" + size()); + } + } + + //----------------------------------------------------------------------- + /** + * Implements an AVLNode which keeps the offset updated. + *

              + * This node contains the real work. + * TreeList is just there to implement {@link java.util.List}. + * The nodes don't know the index of the object they are holding. They + * do know however their position relative to their parent node. + * This allows to calculate the index of a node while traversing the tree. + *

              + * The Faedelung calculation stores a flag for both the left and right child + * to indicate if they are a child (false) or a link as in linked list (true). + */ + static class AVLNode { + /** The left child node or the predecessor if {@link #leftIsPrevious}.*/ + private AVLNode left; + /** Flag indicating that left reference is not a subtree but the predecessor. */ + private boolean leftIsPrevious; + /** The right child node or the successor if {@link #rightIsNext}. */ + private AVLNode right; + /** Flag indicating that right reference is not a subtree but the successor. */ + private boolean rightIsNext; + /** How many levels of left/right are below this one. */ + private int height; + /** The relative position, root holds absolute position. */ + private int relativePosition; + /** The stored element. */ + private E value; + + /** + * Constructs a new node with a relative position. + * + * @param relativePosition the relative position of the node + * @param obj the value for the node + * @param rightFollower the node with the value following this one + * @param leftFollower the node with the value leading this one + */ + private AVLNode(final int relativePosition, final E obj, + final AVLNode rightFollower, final AVLNode leftFollower) { + this.relativePosition = relativePosition; + value = obj; + rightIsNext = true; + leftIsPrevious = true; + right = rightFollower; + left = leftFollower; + } + + /** + * Constructs a new AVL tree from a collection. + *

              + * The collection must be nonempty. + * + * @param coll a nonempty collection + */ + private AVLNode(final Collection coll) { + this(coll.iterator(), 0, coll.size() - 1, 0, null, null); + } + + /** + * Constructs a new AVL tree from a collection. + *

              + * This is a recursive helper for {@link #AVLNode(Collection)}. A call + * to this method will construct the subtree for elements {@code start} + * through {@code end} of the collection, assuming the iterator + * {@code e} already points at element {@code start}. + * + * @param iterator an iterator over the collection, which should already point + * to the element at index {@code start} within the collection + * @param start the index of the first element in the collection that + * should be in this subtree + * @param end the index of the last element in the collection that + * should be in this subtree + * @param absolutePositionOfParent absolute position of this node's + * parent, or 0 if this node is the root + * @param prev the {@code AVLNode} corresponding to element (start - 1) + * of the collection, or null if start is 0 + * @param next the {@code AVLNode} corresponding to element (end + 1) + * of the collection, or null if end is the last element of the collection + */ + private AVLNode(final Iterator iterator, final int start, final int end, + final int absolutePositionOfParent, final AVLNode prev, final AVLNode next) { + final int mid = start + (end - start) / 2; + if (start < mid) { + left = new AVLNode(iterator, start, mid - 1, mid, prev, this); + } else { + leftIsPrevious = true; + left = prev; + } + value = iterator.next(); + relativePosition = mid - absolutePositionOfParent; + if (mid < end) { + right = new AVLNode(iterator, mid + 1, end, mid, this, next); + } else { + rightIsNext = true; + right = next; + } + recalcHeight(); + } + + /** + * Gets the value. + * + * @return the value of this node + */ + E getValue() { + return value; + } + + /** + * Sets the value. + * + * @param obj the value to store + */ + void setValue(final E obj) { + this.value = obj; + } + + /** + * Locate the element with the given index relative to the + * offset of the parent of this node. + */ + AVLNode get(final int index) { + final int indexRelativeToMe = index - relativePosition; + + if (indexRelativeToMe == 0) { + return this; + } + + final AVLNode nextNode = indexRelativeToMe < 0 ? getLeftSubTree() : getRightSubTree(); + if (nextNode == null) { + return null; + } + return nextNode.get(indexRelativeToMe); + } + + /** + * Locate the index that contains the specified object. + */ + int indexOf(final Object object, final int index) { + if (getLeftSubTree() != null) { + final int result = left.indexOf(object, index + left.relativePosition); + if (result != -1) { + return result; + } + } + if (value == null ? value == object : value.equals(object)) { + return index; + } + if (getRightSubTree() != null) { + return right.indexOf(object, index + right.relativePosition); + } + return -1; + } + + /** + * Stores the node and its children into the array specified. + * + * @param array the array to be filled + * @param index the index of this node + */ + void toArray(final Object[] array, final int index) { + array[index] = value; + if (getLeftSubTree() != null) { + left.toArray(array, index + left.relativePosition); + } + if (getRightSubTree() != null) { + right.toArray(array, index + right.relativePosition); + } + } + + /** + * Gets the next node in the list after this one. + * + * @return the next node + */ + AVLNode next() { + if (rightIsNext || right == null) { + return right; + } + return right.min(); + } + + /** + * Gets the node in the list before this one. + * + * @return the previous node + */ + AVLNode previous() { + if (leftIsPrevious || left == null) { + return left; + } + return left.max(); + } + + /** + * Inserts a node at the position index. + * + * @param index is the index of the position relative to the position of + * the parent node. + * @param obj is the object to be stored in the position. + */ + AVLNode insert(final int index, final E obj) { + final int indexRelativeToMe = index - relativePosition; + + if (indexRelativeToMe <= 0) { + return insertOnLeft(indexRelativeToMe, obj); + } + return insertOnRight(indexRelativeToMe, obj); + } + + private AVLNode insertOnLeft(final int indexRelativeToMe, final E obj) { + if (getLeftSubTree() == null) { + setLeft(new AVLNode(-1, obj, this, left), null); + } else { + setLeft(left.insert(indexRelativeToMe, obj), null); + } + + if (relativePosition >= 0) { + relativePosition++; + } + final AVLNode ret = balance(); + recalcHeight(); + return ret; + } + + private AVLNode insertOnRight(final int indexRelativeToMe, final E obj) { + if (getRightSubTree() == null) { + setRight(new AVLNode(+1, obj, right, this), null); + } else { + setRight(right.insert(indexRelativeToMe, obj), null); + } + if (relativePosition < 0) { + relativePosition--; + } + final AVLNode ret = balance(); + recalcHeight(); + return ret; + } + + //----------------------------------------------------------------------- + /** + * Gets the left node, returning null if its a faedelung. + */ + private AVLNode getLeftSubTree() { + return leftIsPrevious ? null : left; + } + + /** + * Gets the right node, returning null if its a faedelung. + */ + private AVLNode getRightSubTree() { + return rightIsNext ? null : right; + } + + /** + * Gets the rightmost child of this node. + * + * @return the rightmost child (greatest index) + */ + private AVLNode max() { + return getRightSubTree() == null ? this : right.max(); + } + + /** + * Gets the leftmost child of this node. + * + * @return the leftmost child (smallest index) + */ + private AVLNode min() { + return getLeftSubTree() == null ? this : left.min(); + } + + /** + * Removes the node at a given position. + * + * @param index is the index of the element to be removed relative to the position of + * the parent node of the current node. + */ + AVLNode remove(final int index) { + final int indexRelativeToMe = index - relativePosition; + + if (indexRelativeToMe == 0) { + return removeSelf(); + } + if (indexRelativeToMe > 0) { + setRight(right.remove(indexRelativeToMe), right.right); + if (relativePosition < 0) { + relativePosition++; + } + } else { + setLeft(left.remove(indexRelativeToMe), left.left); + if (relativePosition > 0) { + relativePosition--; + } + } + recalcHeight(); + return balance(); + } + + private AVLNode removeMax() { + if (getRightSubTree() == null) { + return removeSelf(); + } + setRight(right.removeMax(), right.right); + if (relativePosition < 0) { + relativePosition++; + } + recalcHeight(); + return balance(); + } + + private AVLNode removeMin() { + if (getLeftSubTree() == null) { + return removeSelf(); + } + setLeft(left.removeMin(), left.left); + if (relativePosition > 0) { + relativePosition--; + } + recalcHeight(); + return balance(); + } + + /** + * Removes this node from the tree. + * + * @return the node that replaces this one in the parent + */ + private AVLNode removeSelf() { + if (getRightSubTree() == null && getLeftSubTree() == null) { + return null; + } + if (getRightSubTree() == null) { + if (relativePosition > 0) { + left.relativePosition += relativePosition + (relativePosition > 0 ? 0 : 1); + } + left.max().setRight(null, right); + return left; + } + if (getLeftSubTree() == null) { + right.relativePosition += relativePosition - (relativePosition < 0 ? 0 : 1); + right.min().setLeft(null, left); + return right; + } + + if (heightRightMinusLeft() > 0) { + // more on the right, so delete from the right + final AVLNode rightMin = right.min(); + value = rightMin.value; + if (leftIsPrevious) { + left = rightMin.left; + } + right = right.removeMin(); + if (relativePosition < 0) { + relativePosition++; + } + } else { + // more on the left or equal, so delete from the left + final AVLNode leftMax = left.max(); + value = leftMax.value; + if (rightIsNext) { + right = leftMax.right; + } + final AVLNode leftPrevious = left.left; + left = left.removeMax(); + if (left == null) { + // special case where left that was deleted was a double link + // only occurs when height difference is equal + left = leftPrevious; + leftIsPrevious = true; + } + if (relativePosition > 0) { + relativePosition--; + } + } + recalcHeight(); + return this; + } + + //----------------------------------------------------------------------- + /** + * Balances according to the AVL algorithm. + */ + private AVLNode balance() { + switch (heightRightMinusLeft()) { + case 1 : + case 0 : + case -1 : + return this; + case -2 : + if (left.heightRightMinusLeft() > 0) { + setLeft(left.rotateLeft(), null); + } + return rotateRight(); + case 2 : + if (right.heightRightMinusLeft() < 0) { + setRight(right.rotateRight(), null); + } + return rotateLeft(); + default : + throw new RuntimeException("tree inconsistent!"); + } + } + + /** + * Gets the relative position. + */ + private int getOffset(final AVLNode node) { + if (node == null) { + return 0; + } + return node.relativePosition; + } + + /** + * Sets the relative position. + */ + private int setOffset(final AVLNode node, final int newOffest) { + if (node == null) { + return 0; + } + final int oldOffset = getOffset(node); + node.relativePosition = newOffest; + return oldOffset; + } + + /** + * Sets the height by calculation. + */ + private void recalcHeight() { + height = Math.max( + getLeftSubTree() == null ? -1 : getLeftSubTree().height, + getRightSubTree() == null ? -1 : getRightSubTree().height) + 1; + } + + /** + * Returns the height of the node or -1 if the node is null. + */ + private int getHeight(final AVLNode node) { + return node == null ? -1 : node.height; + } + + /** + * Returns the height difference right - left + */ + private int heightRightMinusLeft() { + return getHeight(getRightSubTree()) - getHeight(getLeftSubTree()); + } + + private AVLNode rotateLeft() { + final AVLNode newTop = right; // can't be faedelung! + final AVLNode movedNode = getRightSubTree().getLeftSubTree(); + + final int newTopPosition = relativePosition + getOffset(newTop); + final int myNewPosition = -newTop.relativePosition; + final int movedPosition = getOffset(newTop) + getOffset(movedNode); + + setRight(movedNode, newTop); + newTop.setLeft(this, null); + + setOffset(newTop, newTopPosition); + setOffset(this, myNewPosition); + setOffset(movedNode, movedPosition); + return newTop; + } + + private AVLNode rotateRight() { + final AVLNode newTop = left; // can't be faedelung + final AVLNode movedNode = getLeftSubTree().getRightSubTree(); + + final int newTopPosition = relativePosition + getOffset(newTop); + final int myNewPosition = -newTop.relativePosition; + final int movedPosition = getOffset(newTop) + getOffset(movedNode); + + setLeft(movedNode, newTop); + newTop.setRight(this, null); + + setOffset(newTop, newTopPosition); + setOffset(this, myNewPosition); + setOffset(movedNode, movedPosition); + return newTop; + } + + /** + * Sets the left field to the node, or the previous node if that is null + * + * @param node the new left subtree node + * @param previous the previous node in the linked list + */ + private void setLeft(final AVLNode node, final AVLNode previous) { + leftIsPrevious = node == null; + left = leftIsPrevious ? previous : node; + recalcHeight(); + } + + /** + * Sets the right field to the node, or the next node if that is null + * + * @param node the new left subtree node + * @param next the next node in the linked list + */ + private void setRight(final AVLNode node, final AVLNode next) { + rightIsNext = node == null; + right = rightIsNext ? next : node; + recalcHeight(); + } + + /** + * Appends the elements of another tree list to this tree list by efficiently + * merging the two AVL trees. This operation is destructive to both trees and + * runs in O(log(m + n)) time. + * + * @param otherTree + * the root of the AVL tree to merge with this one + * @param currentSize + * the number of elements in this AVL tree + * @return the root of the new, merged AVL tree + */ + private AVLNode addAll(AVLNode otherTree, final int currentSize) { + final AVLNode maxNode = max(); + final AVLNode otherTreeMin = otherTree.min(); + + // We need to efficiently merge the two AVL trees while keeping them + // balanced (or nearly balanced). To do this, we take the shorter + // tree and combine it with a similar-height subtree of the taller + // tree. There are two symmetric cases: + // * this tree is taller, or + // * otherTree is taller. + if (otherTree.height > height) { + // CASE 1: The other tree is taller than this one. We will thus + // merge this tree into otherTree. + + // STEP 1: Remove the maximum element from this tree. + final AVLNode leftSubTree = removeMax(); + + // STEP 2: Navigate left from the root of otherTree until we + // find a subtree, s, that is no taller than me. (While we are + // navigating left, we store the nodes we encounter in a stack + // so that we can re-balance them in step 4.) + final Deque> sAncestors = new ArrayDeque>(); + AVLNode s = otherTree; + int sAbsolutePosition = s.relativePosition + currentSize; + int sParentAbsolutePosition = 0; + while (s != null && s.height > getHeight(leftSubTree)) { + sParentAbsolutePosition = sAbsolutePosition; + sAncestors.push(s); + s = s.left; + if (s != null) { + sAbsolutePosition += s.relativePosition; + } + } + + // STEP 3: Replace s with a newly constructed subtree whose root + // is maxNode, whose left subtree is leftSubTree, and whose right + // subtree is s. + maxNode.setLeft(leftSubTree, null); + maxNode.setRight(s, otherTreeMin); + if (leftSubTree != null) { + leftSubTree.max().setRight(null, maxNode); + leftSubTree.relativePosition -= currentSize - 1; + } + if (s != null) { + s.min().setLeft(null, maxNode); + s.relativePosition = sAbsolutePosition - currentSize + 1; + } + maxNode.relativePosition = currentSize - 1 - sParentAbsolutePosition; + otherTree.relativePosition += currentSize; + + // STEP 4: Re-balance the tree and recalculate the heights of s's ancestors. + s = maxNode; + while (!sAncestors.isEmpty()) { + final AVLNode sAncestor = sAncestors.pop(); + sAncestor.setLeft(s, null); + s = sAncestor.balance(); + } + return s; + } + otherTree = otherTree.removeMin(); + + final Deque> sAncestors = new ArrayDeque>(); + AVLNode s = this; + int sAbsolutePosition = s.relativePosition; + int sParentAbsolutePosition = 0; + while (s != null && s.height > getHeight(otherTree)) { + sParentAbsolutePosition = sAbsolutePosition; + sAncestors.push(s); + s = s.right; + if (s != null) { + sAbsolutePosition += s.relativePosition; + } + } + + otherTreeMin.setRight(otherTree, null); + otherTreeMin.setLeft(s, maxNode); + if (otherTree != null) { + otherTree.min().setLeft(null, otherTreeMin); + otherTree.relativePosition++; + } + if (s != null) { + s.max().setRight(null, otherTreeMin); + s.relativePosition = sAbsolutePosition - currentSize; + } + otherTreeMin.relativePosition = currentSize - sParentAbsolutePosition; + + s = otherTreeMin; + while (!sAncestors.isEmpty()) { + final AVLNode sAncestor = sAncestors.pop(); + sAncestor.setRight(s, null); + s = sAncestor.balance(); + } + return s; + } + +// private void checkFaedelung() { +// AVLNode maxNode = left.max(); +// if (!maxNode.rightIsFaedelung || maxNode.right != this) { +// throw new RuntimeException(maxNode + " should right-faedel to " + this); +// } +// AVLNode minNode = right.min(); +// if (!minNode.leftIsFaedelung || minNode.left != this) { +// throw new RuntimeException(maxNode + " should left-faedel to " + this); +// } +// } +// +// private int checkTreeDepth() { +// int hright = (getRightSubTree() == null ? -1 : getRightSubTree().checkTreeDepth()); +// // System.out.print("checkTreeDepth"); +// // System.out.print(this); +// // System.out.print(" left: "); +// // System.out.print(_left); +// // System.out.print(" right: "); +// // System.out.println(_right); +// +// int hleft = (left == null ? -1 : left.checkTreeDepth()); +// if (height != Math.max(hright, hleft) + 1) { +// throw new RuntimeException( +// "height should be max" + hleft + "," + hright + " but is " + height); +// } +// return height; +// } +// +// private int checkLeftSubNode() { +// if (getLeftSubTree() == null) { +// return 0; +// } +// int count = 1 + left.checkRightSubNode(); +// if (left.relativePosition != -count) { +// throw new RuntimeException(); +// } +// return count + left.checkLeftSubNode(); +// } +// +// private int checkRightSubNode() { +// AVLNode right = getRightSubTree(); +// if (right == null) { +// return 0; +// } +// int count = 1; +// count += right.checkLeftSubNode(); +// if (right.relativePosition != count) { +// throw new RuntimeException(); +// } +// return count + right.checkRightSubNode(); +// } + + /** + * Used for debugging. + */ + @Override + public String toString() { + return new StringBuilder() + .append("AVLNode(") + .append(relativePosition) + .append(',') + .append(left != null) + .append(',') + .append(value) + .append(',') + .append(getRightSubTree() != null) + .append(", faedelung ") + .append(rightIsNext) + .append(" )") + .toString(); + } + } + + /** + * A list iterator over the linked list. + */ + static class TreeListIterator implements ListIterator, OrderedIterator { + /** The parent list */ + private final TreeList parent; + /** + * Cache of the next node that will be returned by {@link #next()}. + */ + private AVLNode next; + /** + * The index of the next node to be returned. + */ + private int nextIndex; + /** + * Cache of the last node that was returned by {@link #next()} + * or {@link #previous()}. + */ + private AVLNode current; + /** + * The index of the last node that was returned. + */ + private int currentIndex; + /** + * The modification count that the list is expected to have. If the list + * doesn't have this count, then a + * {@link java.util.ConcurrentModificationException} may be thrown by + * the operations. + */ + private int expectedModCount; + + /** + * Create a ListIterator for a list. + * + * @param parent the parent list + * @param fromIndex the index to start at + */ + protected TreeListIterator(final TreeList parent, final int fromIndex) throws IndexOutOfBoundsException { + super(); + this.parent = parent; + this.expectedModCount = parent.modCount; + this.next = parent.root == null ? null : parent.root.get(fromIndex); + this.nextIndex = fromIndex; + this.currentIndex = -1; + } + + /** + * Checks the modification count of the list is the value that this + * object expects. + * + * @throws ConcurrentModificationException If the list's modification + * count isn't the value that was expected. + */ + protected void checkModCount() { + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + public boolean hasNext() { + return nextIndex < parent.size(); + } + + public E next() { + checkModCount(); + if (!hasNext()) { + throw new NoSuchElementException("No element at index " + nextIndex + "."); + } + if (next == null) { + next = parent.root.get(nextIndex); + } + final E value = next.getValue(); + current = next; + currentIndex = nextIndex++; + next = next.next(); + return value; + } + + public boolean hasPrevious() { + return nextIndex > 0; + } + + public E previous() { + checkModCount(); + if (!hasPrevious()) { + throw new NoSuchElementException("Already at start of list."); + } + if (next == null) { + next = parent.root.get(nextIndex - 1); + } else { + next = next.previous(); + } + final E value = next.getValue(); + current = next; + currentIndex = --nextIndex; + return value; + } + + public int nextIndex() { + return nextIndex; + } + + public int previousIndex() { + return nextIndex() - 1; + } + + public void remove() { + checkModCount(); + if (currentIndex == -1) { + throw new IllegalStateException(); + } + parent.remove(currentIndex); + if (nextIndex != currentIndex) { + // remove() following next() + nextIndex--; + } + // the AVL node referenced by next may have become stale after a remove + // reset it now: will be retrieved by next call to next()/previous() via nextIndex + next = null; + current = null; + currentIndex = -1; + expectedModCount++; + } + + public void set(final E obj) { + checkModCount(); + if (current == null) { + throw new IllegalStateException(); + } + current.setValue(obj); + } + + public void add(final E obj) { + checkModCount(); + parent.add(nextIndex, obj); + current = null; + currentIndex = -1; + nextIndex++; + expectedModCount++; + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/UnmodifiableList.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/UnmodifiableList.java new file mode 100644 index 000000000..d8ff163b3 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/UnmodifiableList.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; +import org.apache.commons.collections4.iterators.UnmodifiableListIterator; + +/** + * Decorates another List to ensure it can't be altered. + *

              + * This class is Serializable from Commons Collections 3.1. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableList.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableList + extends AbstractSerializableListDecorator + implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = 6595182819922443652L; + + /** + * Factory method to create an unmodifiable list. + * + * @param the type of the elements in the list + * @param list the list to decorate, must not be null + * @return a new unmodifiable list + * @throws NullPointerException if list is null + * @since 4.0 + */ + public static List unmodifiableList(final List list) { + if (list instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final List tmpList = (List) list; + return tmpList; + } + return new UnmodifiableList(list); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param list the list to decorate, must not be null + * @throws NullPointerException if list is null + */ + @SuppressWarnings("unchecked") // safe to upcast + public UnmodifiableList(final List list) { + super((List) list); + } + + //----------------------------------------------------------------------- + @Override + public Iterator iterator() { + return UnmodifiableIterator.unmodifiableIterator(decorated().iterator()); + } + + @Override + public boolean add(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + @Override + public ListIterator listIterator() { + return UnmodifiableListIterator.umodifiableListIterator(decorated().listIterator()); + } + + @Override + public ListIterator listIterator(final int index) { + return UnmodifiableListIterator.umodifiableListIterator(decorated().listIterator(index)); + } + + @Override + public void add(final int index, final E object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final int index, final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public E remove(final int index) { + throw new UnsupportedOperationException(); + } + + @Override + public E set(final int index, final E object) { + throw new UnsupportedOperationException(); + } + + @Override + public List subList(final int fromIndex, final int toIndex) { + final List sub = decorated().subList(fromIndex, toIndex); + return new UnmodifiableList(sub); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/list/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/list/package-info.java new file mode 100644 index 000000000..133e80d17 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/list/package-info.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the {@link java.util.List List} interface. + *

              + * The following implementations are provided in the package: + *

                + *
              • TreeList - a list that is optimised for insertions and removals at any index in the list
              • + *
              • CursorableLinkedList - a list that can be modified while the listIterator (cursor) is being used
              • + *
              • NodeCachingLinkedList - a linked list that caches the storage nodes for a performance gain
              • + *
              + *

              + * The following decorators are provided in the package: + *

                + *
              • Unmodifiable - ensures the collection cannot be altered
              • + *
              • Predicated - ensures that only elements that are valid according to a predicate can be added
              • + *
              • Transformed - transforms each element added
              • + *
              • FixedSize - ensures that the size of the list cannot change
              • + *
              • Lazy - creates objects in the list on demand
              • + *
              • Growth - grows the list instead of erroring when set/add used with index beyond the list size
              • + *
              • SetUnique - a list that avoids duplicate entries like a Set
              • + *
              + * + * @version $Id: package-info.java 1469004 2013-04-17 17:37:03Z tn $ + */ +package org.apache.commons.collections4.list; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractHashedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractHashedMap.java new file mode 100644 index 000000000..a111c4858 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractHashedMap.java @@ -0,0 +1,1389 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.commons.collections4.IterableMap; +import org.apache.commons.collections4.KeyValue; +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.iterators.EmptyIterator; +import org.apache.commons.collections4.iterators.EmptyMapIterator; + +/** + * An abstract implementation of a hash-based map which provides numerous points for + * subclasses to override. + *

              + * This class implements all the features necessary for a subclass hash-based map. + * Key-value entries are stored in instances of the HashEntry class, + * which can be overridden and replaced. The iterators can similarly be replaced, + * without the need to replace the KeySet, EntrySet and Values view classes. + *

              + * Overridable methods are provided to change the default hashing behaviour, and + * to change how entries are added to and removed from the map. Hopefully, all you + * need for unusual subclasses is here. + *

              + * NOTE: From Commons Collections 3.1 this class extends AbstractMap. + * This is to provide backwards compatibility for ReferenceMap between v3.0 and v3.1. + * This extends clause will be removed in v5.0. + * + * @since 3.0 + * @version $Id: AbstractHashedMap.java 1649010 2015-01-02 12:32:37Z tn $ + */ +public class AbstractHashedMap extends AbstractMap implements IterableMap { + + protected static final String NO_NEXT_ENTRY = "No next() entry in the iteration"; + protected static final String NO_PREVIOUS_ENTRY = "No previous() entry in the iteration"; + protected static final String REMOVE_INVALID = "remove() can only be called once after next()"; + protected static final String GETKEY_INVALID = "getKey() can only be called after next() and before remove()"; + protected static final String GETVALUE_INVALID = "getValue() can only be called after next() and before remove()"; + protected static final String SETVALUE_INVALID = "setValue() can only be called after next() and before remove()"; + + /** The default capacity to use */ + protected static final int DEFAULT_CAPACITY = 16; + /** The default threshold to use */ + protected static final int DEFAULT_THRESHOLD = 12; + /** The default load factor to use */ + protected static final float DEFAULT_LOAD_FACTOR = 0.75f; + /** The maximum capacity allowed */ + protected static final int MAXIMUM_CAPACITY = 1 << 30; + /** An object for masking null */ + protected static final Object NULL = new Object(); + + /** Load factor, normally 0.75 */ + transient float loadFactor; + /** The size of the map */ + transient int size; + /** Map entries */ + transient HashEntry[] data; + /** Size at which to rehash */ + transient int threshold; + /** Modification count for iterators */ + transient int modCount; + /** Entry set */ + transient EntrySet entrySet; + /** Key set */ + transient KeySet keySet; + /** Values */ + transient Values values; + + /** + * Constructor only used in deserialization, do not use otherwise. + */ + protected AbstractHashedMap() { + super(); + } + + /** + * Constructor which performs no validation on the passed in parameters. + * + * @param initialCapacity the initial capacity, must be a power of two + * @param loadFactor the load factor, must be > 0.0f and generally < 1.0f + * @param threshold the threshold, must be sensible + */ + @SuppressWarnings("unchecked") + protected AbstractHashedMap(final int initialCapacity, final float loadFactor, final int threshold) { + super(); + this.loadFactor = loadFactor; + this.data = new HashEntry[initialCapacity]; + this.threshold = threshold; + init(); + } + + /** + * Constructs a new, empty map with the specified initial capacity and + * default load factor. + * + * @param initialCapacity the initial capacity + * @throws IllegalArgumentException if the initial capacity is negative + */ + protected AbstractHashedMap(final int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs a new, empty map with the specified initial capacity and + * load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @throws IllegalArgumentException if the initial capacity is negative + * @throws IllegalArgumentException if the load factor is less than or equal to zero + */ + @SuppressWarnings("unchecked") + protected AbstractHashedMap(int initialCapacity, final float loadFactor) { + super(); + if (initialCapacity < 0) { + throw new IllegalArgumentException("Initial capacity must be a non negative number"); + } + if (loadFactor <= 0.0f || Float.isNaN(loadFactor)) { + throw new IllegalArgumentException("Load factor must be greater than 0"); + } + this.loadFactor = loadFactor; + initialCapacity = calculateNewCapacity(initialCapacity); + this.threshold = calculateThreshold(initialCapacity, loadFactor); + this.data = new HashEntry[initialCapacity]; + init(); + } + + /** + * Constructor copying elements from another map. + * + * @param map the map to copy + * @throws NullPointerException if the map is null + */ + protected AbstractHashedMap(final Map map) { + this(Math.max(2 * map.size(), DEFAULT_CAPACITY), DEFAULT_LOAD_FACTOR); + _putAll(map); + } + + /** + * Initialise subclasses during construction, cloning or deserialization. + */ + protected void init() { + } + + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the key specified. + * + * @param key the key + * @return the mapped value, null if no match + */ + @Override + public V get(Object key) { + key = convertKey(key); + final int hashCode = hash(key); + HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) { + return entry.getValue(); + } + entry = entry.next; + } + return null; + } + + /** + * Gets the size of the map. + * + * @return the size + */ + @Override + public int size() { + return size; + } + + /** + * Checks whether the map is currently empty. + * + * @return true if the map is currently size zero + */ + @Override + public boolean isEmpty() { + return size == 0; + } + + //----------------------------------------------------------------------- + /** + * Checks whether the map contains the specified key. + * + * @param key the key to search for + * @return true if the map contains the key + */ + @Override + public boolean containsKey(Object key) { + key = convertKey(key); + final int hashCode = hash(key); + HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) { + return true; + } + entry = entry.next; + } + return false; + } + + /** + * Checks whether the map contains the specified value. + * + * @param value the value to search for + * @return true if the map contains the value + */ + @Override + public boolean containsValue(final Object value) { + if (value == null) { + for (final HashEntry element : data) { + HashEntry entry = element; + while (entry != null) { + if (entry.getValue() == null) { + return true; + } + entry = entry.next; + } + } + } else { + for (final HashEntry element : data) { + HashEntry entry = element; + while (entry != null) { + if (isEqualValue(value, entry.getValue())) { + return true; + } + entry = entry.next; + } + } + } + return false; + } + + //----------------------------------------------------------------------- + /** + * Puts a key-value mapping into this map. + * + * @param key the key to add + * @param value the value to add + * @return the value previously mapped to this key, null if none + */ + @Override + public V put(final K key, final V value) { + final Object convertedKey = convertKey(key); + final int hashCode = hash(convertedKey); + final int index = hashIndex(hashCode, data.length); + HashEntry entry = data[index]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(convertedKey, entry.key)) { + final V oldValue = entry.getValue(); + updateEntry(entry, value); + return oldValue; + } + entry = entry.next; + } + + addMapping(index, hashCode, key, value); + return null; + } + + /** + * Puts all the values from the specified map into this map. + *

              + * This implementation iterates around the specified map and + * uses {@link #put(Object, Object)}. + * + * @param map the map to add + * @throws NullPointerException if the map is null + */ + @Override + public void putAll(final Map map) { + _putAll(map); + } + + /** + * Puts all the values from the specified map into this map. + *

              + * This implementation iterates around the specified map and + * uses {@link #put(Object, Object)}. + *

              + * It is private to allow the constructor to still call it + * even when putAll is overriden. + * + * @param map the map to add + * @throws NullPointerException if the map is null + */ + private void _putAll(final Map map) { + final int mapSize = map.size(); + if (mapSize == 0) { + return; + } + final int newSize = (int) ((size + mapSize) / loadFactor + 1); + ensureCapacity(calculateNewCapacity(newSize)); + for (final Map.Entry entry: map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + /** + * Removes the specified mapping from this map. + * + * @param key the mapping to remove + * @return the value mapped to the removed key, null if key not in map + */ + @Override + public V remove(Object key) { + key = convertKey(key); + final int hashCode = hash(key); + final int index = hashIndex(hashCode, data.length); + HashEntry entry = data[index]; + HashEntry previous = null; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) { + final V oldValue = entry.getValue(); + removeMapping(entry, index, previous); + return oldValue; + } + previous = entry; + entry = entry.next; + } + return null; + } + + /** + * Clears the map, resetting the size to zero and nullifying references + * to avoid garbage collection issues. + */ + @Override + public void clear() { + modCount++; + final HashEntry[] data = this.data; + for (int i = data.length - 1; i >= 0; i--) { + data[i] = null; + } + size = 0; + } + + //----------------------------------------------------------------------- + /** + * Converts input keys to another object for storage in the map. + * This implementation masks nulls. + * Subclasses can override this to perform alternate key conversions. + *

              + * The reverse conversion can be changed, if required, by overriding the + * getKey() method in the hash entry. + * + * @param key the key convert + * @return the converted key + */ + protected Object convertKey(final Object key) { + return key == null ? NULL : key; + } + + /** + * Gets the hash code for the key specified. + * This implementation uses the additional hashing routine from JDK1.4. + * Subclasses can override this to return alternate hash codes. + * + * @param key the key to get a hash code for + * @return the hash code + */ + protected int hash(final Object key) { + // same as JDK 1.4 + int h = key.hashCode(); + h += ~(h << 9); + h ^= h >>> 14; + h += h << 4; + h ^= h >>> 10; + return h; + } + + /** + * Compares two keys, in internal converted form, to see if they are equal. + * This implementation uses the equals method and assumes neither key is null. + * Subclasses can override this to match differently. + * + * @param key1 the first key to compare passed in from outside + * @param key2 the second key extracted from the entry via entry.key + * @return true if equal + */ + protected boolean isEqualKey(final Object key1, final Object key2) { + return key1 == key2 || key1.equals(key2); + } + + /** + * Compares two values, in external form, to see if they are equal. + * This implementation uses the equals method and assumes neither value is null. + * Subclasses can override this to match differently. + * + * @param value1 the first value to compare passed in from outside + * @param value2 the second value extracted from the entry via getValue() + * @return true if equal + */ + protected boolean isEqualValue(final Object value1, final Object value2) { + return value1 == value2 || value1.equals(value2); + } + + /** + * Gets the index into the data storage for the hashCode specified. + * This implementation uses the least significant bits of the hashCode. + * Subclasses can override this to return alternate bucketing. + * + * @param hashCode the hash code to use + * @param dataSize the size of the data to pick a bucket from + * @return the bucket index + */ + protected int hashIndex(final int hashCode, final int dataSize) { + return hashCode & dataSize - 1; + } + + //----------------------------------------------------------------------- + /** + * Gets the entry mapped to the key specified. + *

              + * This method exists for subclasses that may need to perform a multi-step + * process accessing the entry. The public methods in this class don't use this + * method to gain a small performance boost. + * + * @param key the key + * @return the entry, null if no match + */ + protected HashEntry getEntry(Object key) { + key = convertKey(key); + final int hashCode = hash(key); + HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) { + return entry; + } + entry = entry.next; + } + return null; + } + + //----------------------------------------------------------------------- + /** + * Updates an existing key-value mapping to change the value. + *

              + * This implementation calls setValue() on the entry. + * Subclasses could override to handle changes to the map. + * + * @param entry the entry to update + * @param newValue the new value to store + */ + protected void updateEntry(final HashEntry entry, final V newValue) { + entry.setValue(newValue); + } + + /** + * Reuses an existing key-value mapping, storing completely new data. + *

              + * This implementation sets all the data fields on the entry. + * Subclasses could populate additional entry fields. + * + * @param entry the entry to update, not null + * @param hashIndex the index in the data array + * @param hashCode the hash code of the key to add + * @param key the key to add + * @param value the value to add + */ + protected void reuseEntry(final HashEntry entry, final int hashIndex, final int hashCode, + final K key, final V value) { + entry.next = data[hashIndex]; + entry.hashCode = hashCode; + entry.key = key; + entry.value = value; + } + + //----------------------------------------------------------------------- + /** + * Adds a new key-value mapping into this map. + *

              + * This implementation calls createEntry(), addEntry() + * and checkCapacity(). + * It also handles changes to modCount and size. + * Subclasses could override to fully control adds to the map. + * + * @param hashIndex the index into the data array to store at + * @param hashCode the hash code of the key to add + * @param key the key to add + * @param value the value to add + */ + protected void addMapping(final int hashIndex, final int hashCode, final K key, final V value) { + modCount++; + final HashEntry entry = createEntry(data[hashIndex], hashCode, key, value); + addEntry(entry, hashIndex); + size++; + checkCapacity(); + } + + /** + * Creates an entry to store the key-value data. + *

              + * This implementation creates a new HashEntry instance. + * Subclasses can override this to return a different storage class, + * or implement caching. + * + * @param next the next entry in sequence + * @param hashCode the hash code to use + * @param key the key to store + * @param value the value to store + * @return the newly created entry + */ + protected HashEntry createEntry(final HashEntry next, final int hashCode, final K key, final V value) { + return new HashEntry(next, hashCode, convertKey(key), value); + } + + /** + * Adds an entry into this map. + *

              + * This implementation adds the entry to the data storage table. + * Subclasses could override to handle changes to the map. + * + * @param entry the entry to add + * @param hashIndex the index into the data array to store at + */ + protected void addEntry(final HashEntry entry, final int hashIndex) { + data[hashIndex] = entry; + } + + //----------------------------------------------------------------------- + /** + * Removes a mapping from the map. + *

              + * This implementation calls removeEntry() and destroyEntry(). + * It also handles changes to modCount and size. + * Subclasses could override to fully control removals from the map. + * + * @param entry the entry to remove + * @param hashIndex the index into the data structure + * @param previous the previous entry in the chain + */ + protected void removeMapping(final HashEntry entry, final int hashIndex, final HashEntry previous) { + modCount++; + removeEntry(entry, hashIndex, previous); + size--; + destroyEntry(entry); + } + + /** + * Removes an entry from the chain stored in a particular index. + *

              + * This implementation removes the entry from the data storage table. + * The size is not updated. + * Subclasses could override to handle changes to the map. + * + * @param entry the entry to remove + * @param hashIndex the index into the data structure + * @param previous the previous entry in the chain + */ + protected void removeEntry(final HashEntry entry, final int hashIndex, final HashEntry previous) { + if (previous == null) { + data[hashIndex] = entry.next; + } else { + previous.next = entry.next; + } + } + + /** + * Kills an entry ready for the garbage collector. + *

              + * This implementation prepares the HashEntry for garbage collection. + * Subclasses can override this to implement caching (override clear as well). + * + * @param entry the entry to destroy + */ + protected void destroyEntry(final HashEntry entry) { + entry.next = null; + entry.key = null; + entry.value = null; + } + + //----------------------------------------------------------------------- + /** + * Checks the capacity of the map and enlarges it if necessary. + *

              + * This implementation uses the threshold to check if the map needs enlarging + */ + protected void checkCapacity() { + if (size >= threshold) { + final int newCapacity = data.length * 2; + if (newCapacity <= MAXIMUM_CAPACITY) { + ensureCapacity(newCapacity); + } + } + } + + /** + * Changes the size of the data structure to the capacity proposed. + * + * @param newCapacity the new capacity of the array (a power of two, less or equal to max) + */ + @SuppressWarnings("unchecked") + protected void ensureCapacity(final int newCapacity) { + final int oldCapacity = data.length; + if (newCapacity <= oldCapacity) { + return; + } + if (size == 0) { + threshold = calculateThreshold(newCapacity, loadFactor); + data = new HashEntry[newCapacity]; + } else { + final HashEntry oldEntries[] = data; + final HashEntry newEntries[] = new HashEntry[newCapacity]; + + modCount++; + for (int i = oldCapacity - 1; i >= 0; i--) { + HashEntry entry = oldEntries[i]; + if (entry != null) { + oldEntries[i] = null; // gc + do { + final HashEntry next = entry.next; + final int index = hashIndex(entry.hashCode, newCapacity); + entry.next = newEntries[index]; + newEntries[index] = entry; + entry = next; + } while (entry != null); + } + } + threshold = calculateThreshold(newCapacity, loadFactor); + data = newEntries; + } + } + + /** + * Calculates the new capacity of the map. + * This implementation normalizes the capacity to a power of two. + * + * @param proposedCapacity the proposed capacity + * @return the normalized new capacity + */ + protected int calculateNewCapacity(final int proposedCapacity) { + int newCapacity = 1; + if (proposedCapacity > MAXIMUM_CAPACITY) { + newCapacity = MAXIMUM_CAPACITY; + } else { + while (newCapacity < proposedCapacity) { + newCapacity <<= 1; // multiply by two + } + if (newCapacity > MAXIMUM_CAPACITY) { + newCapacity = MAXIMUM_CAPACITY; + } + } + return newCapacity; + } + + /** + * Calculates the new threshold of the map, where it will be resized. + * This implementation uses the load factor. + * + * @param newCapacity the new capacity + * @param factor the load factor + * @return the new resize threshold + */ + protected int calculateThreshold(final int newCapacity, final float factor) { + return (int) (newCapacity * factor); + } + + //----------------------------------------------------------------------- + /** + * Gets the next field from a HashEntry. + * Used in subclasses that have no visibility of the field. + * + * @param entry the entry to query, must not be null + * @return the next field of the entry + * @throws NullPointerException if the entry is null + * @since 3.1 + */ + protected HashEntry entryNext(final HashEntry entry) { + return entry.next; + } + + /** + * Gets the hashCode field from a HashEntry. + * Used in subclasses that have no visibility of the field. + * + * @param entry the entry to query, must not be null + * @return the hashCode field of the entry + * @throws NullPointerException if the entry is null + * @since 3.1 + */ + protected int entryHashCode(final HashEntry entry) { + return entry.hashCode; + } + + /** + * Gets the key field from a HashEntry. + * Used in subclasses that have no visibility of the field. + * + * @param entry the entry to query, must not be null + * @return the key field of the entry + * @throws NullPointerException if the entry is null + * @since 3.1 + */ + protected K entryKey(final HashEntry entry) { + return entry.getKey(); + } + + /** + * Gets the value field from a HashEntry. + * Used in subclasses that have no visibility of the field. + * + * @param entry the entry to query, must not be null + * @return the value field of the entry + * @throws NullPointerException if the entry is null + * @since 3.1 + */ + protected V entryValue(final HashEntry entry) { + return entry.getValue(); + } + + //----------------------------------------------------------------------- + /** + * Gets an iterator over the map. + * Changes made to the iterator affect this map. + *

              + * A MapIterator returns the keys in the map. It also provides convenient + * methods to get the key and value, and set the value. + * It avoids the need to create an entrySet/keySet/values object. + * It also avoids creating the Map.Entry object. + * + * @return the map iterator + */ + public MapIterator mapIterator() { + if (size == 0) { + return EmptyMapIterator.emptyMapIterator(); + } + return new HashMapIterator(this); + } + + /** + * MapIterator implementation. + */ + protected static class HashMapIterator extends HashIterator implements MapIterator { + + protected HashMapIterator(final AbstractHashedMap parent) { + super(parent); + } + + public K next() { + return super.nextEntry().getKey(); + } + + public K getKey() { + final HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); + } + return current.getKey(); + } + + public V getValue() { + final HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); + } + return current.getValue(); + } + + public V setValue(final V value) { + final HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); + } + return current.setValue(value); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the entrySet view of the map. + * Changes made to the view affect this map. + * To simply iterate through the entries, use {@link #mapIterator()}. + * + * @return the entrySet view + */ + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = new EntrySet(this); + } + return entrySet; + } + + /** + * Creates an entry set iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the entrySet iterator + */ + protected Iterator> createEntrySetIterator() { + if (size() == 0) { + return EmptyIterator.>emptyIterator(); + } + return new EntrySetIterator(this); + } + + /** + * EntrySet implementation. + */ + protected static class EntrySet extends AbstractSet> { + /** The parent map */ + private final AbstractHashedMap parent; + + protected EntrySet(final AbstractHashedMap parent) { + super(); + this.parent = parent; + } + + @Override + public int size() { + return parent.size(); + } + + @Override + public void clear() { + parent.clear(); + } + + @Override + public boolean contains(final Object entry) { + if (entry instanceof Map.Entry) { + final Map.Entry e = (Map.Entry) entry; + final Entry match = parent.getEntry(e.getKey()); + return match != null && match.equals(e); + } + return false; + } + + @Override + public boolean remove(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + if (contains(obj) == false) { + return false; + } + final Map.Entry entry = (Map.Entry) obj; + parent.remove(entry.getKey()); + return true; + } + + @Override + public Iterator> iterator() { + return parent.createEntrySetIterator(); + } + } + + /** + * EntrySet iterator. + */ + protected static class EntrySetIterator extends HashIterator implements Iterator> { + + protected EntrySetIterator(final AbstractHashedMap parent) { + super(parent); + } + + public Map.Entry next() { + return super.nextEntry(); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the keySet view of the map. + * Changes made to the view affect this map. + * To simply iterate through the keys, use {@link #mapIterator()}. + * + * @return the keySet view + */ + @Override + public Set keySet() { + if (keySet == null) { + keySet = new KeySet(this); + } + return keySet; + } + + /** + * Creates a key set iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the keySet iterator + */ + protected Iterator createKeySetIterator() { + if (size() == 0) { + return EmptyIterator.emptyIterator(); + } + return new KeySetIterator(this); + } + + /** + * KeySet implementation. + */ + protected static class KeySet extends AbstractSet { + /** The parent map */ + private final AbstractHashedMap parent; + + protected KeySet(final AbstractHashedMap parent) { + super(); + this.parent = parent; + } + + @Override + public int size() { + return parent.size(); + } + + @Override + public void clear() { + parent.clear(); + } + + @Override + public boolean contains(final Object key) { + return parent.containsKey(key); + } + + @Override + public boolean remove(final Object key) { + final boolean result = parent.containsKey(key); + parent.remove(key); + return result; + } + + @Override + public Iterator iterator() { + return parent.createKeySetIterator(); + } + } + + /** + * KeySet iterator. + */ + protected static class KeySetIterator extends HashIterator implements Iterator { + + @SuppressWarnings("unchecked") + protected KeySetIterator(final AbstractHashedMap parent) { + super((AbstractHashedMap) parent); + } + + public K next() { + return super.nextEntry().getKey(); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the values view of the map. + * Changes made to the view affect this map. + * To simply iterate through the values, use {@link #mapIterator()}. + * + * @return the values view + */ + @Override + public Collection values() { + if (values == null) { + values = new Values(this); + } + return values; + } + + /** + * Creates a values iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the values iterator + */ + protected Iterator createValuesIterator() { + if (size() == 0) { + return EmptyIterator.emptyIterator(); + } + return new ValuesIterator(this); + } + + /** + * Values implementation. + */ + protected static class Values extends AbstractCollection { + /** The parent map */ + private final AbstractHashedMap parent; + + protected Values(final AbstractHashedMap parent) { + super(); + this.parent = parent; + } + + @Override + public int size() { + return parent.size(); + } + + @Override + public void clear() { + parent.clear(); + } + + @Override + public boolean contains(final Object value) { + return parent.containsValue(value); + } + + @Override + public Iterator iterator() { + return parent.createValuesIterator(); + } + } + + /** + * Values iterator. + */ + protected static class ValuesIterator extends HashIterator implements Iterator { + + @SuppressWarnings("unchecked") + protected ValuesIterator(final AbstractHashedMap parent) { + super((AbstractHashedMap) parent); + } + + public V next() { + return super.nextEntry().getValue(); + } + } + + //----------------------------------------------------------------------- + /** + * HashEntry used to store the data. + *

              + * If you subclass AbstractHashedMap but not HashEntry + * then you will not be able to access the protected fields. + * The entryXxx() methods on AbstractHashedMap exist + * to provide the necessary access. + */ + protected static class HashEntry implements Map.Entry, KeyValue { + /** The next entry in the hash chain */ + protected HashEntry next; + /** The hash code of the key */ + protected int hashCode; + /** The key */ + protected Object key; + /** The value */ + protected Object value; + + protected HashEntry(final HashEntry next, final int hashCode, final Object key, final V value) { + super(); + this.next = next; + this.hashCode = hashCode; + this.key = key; + this.value = value; + } + + @SuppressWarnings("unchecked") + public K getKey() { + if (key == NULL) { + return null; + } + return (K) key; + } + + @SuppressWarnings("unchecked") + public V getValue() { + return (V) value; + } + + @SuppressWarnings("unchecked") + public V setValue(final V value) { + final Object old = this.value; + this.value = value; + return (V) old; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry other = (Map.Entry) obj; + return + (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) && + (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue())); + } + + @Override + public int hashCode() { + return (getKey() == null ? 0 : getKey().hashCode()) ^ + (getValue() == null ? 0 : getValue().hashCode()); + } + + @Override + public String toString() { + return new StringBuilder().append(getKey()).append('=').append(getValue()).toString(); + } + } + + /** + * Base Iterator + */ + protected static abstract class HashIterator { + + /** The parent map */ + private final AbstractHashedMap parent; + /** The current index into the array of buckets */ + private int hashIndex; + /** The last returned entry */ + private HashEntry last; + /** The next entry */ + private HashEntry next; + /** The modification count expected */ + private int expectedModCount; + + protected HashIterator(final AbstractHashedMap parent) { + super(); + this.parent = parent; + final HashEntry[] data = parent.data; + int i = data.length; + HashEntry next = null; + while (i > 0 && next == null) { + next = data[--i]; + } + this.next = next; + this.hashIndex = i; + this.expectedModCount = parent.modCount; + } + + public boolean hasNext() { + return next != null; + } + + protected HashEntry nextEntry() { + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + final HashEntry newCurrent = next; + if (newCurrent == null) { + throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); + } + final HashEntry[] data = parent.data; + int i = hashIndex; + HashEntry n = newCurrent.next; + while (n == null && i > 0) { + n = data[--i]; + } + next = n; + hashIndex = i; + last = newCurrent; + return newCurrent; + } + + protected HashEntry currentEntry() { + return last; + } + + public void remove() { + if (last == null) { + throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); + } + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + parent.remove(last.getKey()); + last = null; + expectedModCount = parent.modCount; + } + + @Override + public String toString() { + if (last != null) { + return "Iterator[" + last.getKey() + "=" + last.getValue() + "]"; + } + return "Iterator[]"; + } + } + + //----------------------------------------------------------------------- + /** + * Writes the map data to the stream. This method must be overridden if a + * subclass must be setup before put() is used. + *

              + * Serialization is not one of the JDK's nicest topics. Normal serialization will + * initialise the superclass before the subclass. Sometimes however, this isn't + * what you want, as in this case the put() method on read can be + * affected by subclass state. + *

              + * The solution adopted here is to serialize the state data of this class in + * this protected method. This method must be called by the + * writeObject() of the first serializable subclass. + *

              + * Subclasses may override if they have a specific field that must be present + * on read before this implementation will work. Generally, the read determines + * what must be serialized here, if anything. + * + * @param out the output stream + * @throws IOException if an error occurs while writing tothe stream + */ + protected void doWriteObject(final ObjectOutputStream out) throws IOException { + out.writeFloat(loadFactor); + out.writeInt(data.length); + out.writeInt(size); + for (final MapIterator it = mapIterator(); it.hasNext();) { + out.writeObject(it.next()); + out.writeObject(it.getValue()); + } + } + + /** + * Reads the map data from the stream. This method must be overridden if a + * subclass must be setup before put() is used. + *

              + * Serialization is not one of the JDK's nicest topics. Normal serialization will + * initialise the superclass before the subclass. Sometimes however, this isn't + * what you want, as in this case the put() method on read can be + * affected by subclass state. + *

              + * The solution adopted here is to deserialize the state data of this class in + * this protected method. This method must be called by the + * readObject() of the first serializable subclass. + *

              + * Subclasses may override if the subclass has a specific field that must be present + * before put() or calculateThreshold() will work correctly. + * + * @param in the input stream + * @throws IOException if an error occurs while reading from the stream + * @throws ClassNotFoundException if an object read from the stream can not be loaded + */ + @SuppressWarnings("unchecked") + protected void doReadObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + loadFactor = in.readFloat(); + final int capacity = in.readInt(); + final int size = in.readInt(); + init(); + threshold = calculateThreshold(capacity, loadFactor); + data = new HashEntry[capacity]; + for (int i = 0; i < size; i++) { + final K key = (K) in.readObject(); + final V value = (V) in.readObject(); + put(key, value); + } + } + + //----------------------------------------------------------------------- + /** + * Clones the map without cloning the keys or values. + *

              + * To implement clone(), a subclass must implement the + * Cloneable interface and make this method public. + * + * @return a shallow clone + * @throws InternalError if {@link AbstractMap#clone()} failed + */ + @Override + @SuppressWarnings("unchecked") + protected AbstractHashedMap clone() { + try { + final AbstractHashedMap cloned = (AbstractHashedMap) super.clone(); + cloned.data = new HashEntry[data.length]; + cloned.entrySet = null; + cloned.keySet = null; + cloned.values = null; + cloned.modCount = 0; + cloned.size = 0; + cloned.init(); + cloned.putAll(this); + return cloned; + } catch (final CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + /** + * Compares this map with another. + * + * @param obj the object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map == false) { + return false; + } + final Map map = (Map) obj; + if (map.size() != size()) { + return false; + } + final MapIterator it = mapIterator(); + try { + while (it.hasNext()) { + final Object key = it.next(); + final Object value = it.getValue(); + if (value == null) { + if (map.get(key) != null || map.containsKey(key) == false) { + return false; + } + } else { + if (value.equals(map.get(key)) == false) { + return false; + } + } + } + } catch (final ClassCastException ignored) { + return false; + } catch (final NullPointerException ignored) { + return false; + } + return true; + } + + /** + * Gets the standard Map hashCode. + * + * @return the hash code defined in the Map interface + */ + @Override + public int hashCode() { + int total = 0; + final Iterator> it = createEntrySetIterator(); + while (it.hasNext()) { + total += it.next().hashCode(); + } + return total; + } + + /** + * Gets the map as a String. + * + * @return a string version of the map + */ + @Override + public String toString() { + if (size() == 0) { + return "{}"; + } + final StringBuilder buf = new StringBuilder(32 * size()); + buf.append('{'); + + final MapIterator it = mapIterator(); + boolean hasNext = it.hasNext(); + while (hasNext) { + final K key = it.next(); + final V value = it.getValue(); + buf.append(key == this ? "(this Map)" : key) + .append('=') + .append(value == this ? "(this Map)" : value); + + hasNext = it.hasNext(); + if (hasNext) { + buf.append(',').append(' '); + } + } + + buf.append('}'); + return buf.toString(); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractInputCheckedMapDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractInputCheckedMapDecorator.java new file mode 100644 index 000000000..a862b5d0a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractInputCheckedMapDecorator.java @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.lang.reflect.Array; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; +import org.apache.commons.collections4.keyvalue.AbstractMapEntryDecorator; +import org.apache.commons.collections4.set.AbstractSetDecorator; + +/** + * An abstract base class that simplifies the task of creating map decorators. + *

              + * The Map API is very difficult to decorate correctly, and involves implementing + * lots of different classes. This class exists to provide a simpler API. + *

              + * Special hook methods are provided that are called when objects are added to + * the map. By overriding these methods, the input can be validated or manipulated. + * In addition to the main map methods, the entrySet is also affected, which is + * the hardest part of writing map implementations. + *

              + * This class is package-scoped, and may be withdrawn or replaced in future + * versions of Commons Collections. + * + * @since 3.1 + * @version $Id: AbstractInputCheckedMapDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +abstract class AbstractInputCheckedMapDecorator + extends AbstractMapDecorator { + + /** + * Constructor only used in deserialization, do not use otherwise. + */ + protected AbstractInputCheckedMapDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + protected AbstractInputCheckedMapDecorator(final Map map) { + super(map); + } + + //----------------------------------------------------------------------- + /** + * Hook method called when a value is being set using setValue. + *

              + * An implementation may validate the value and throw an exception + * or it may transform the value into another object. + *

              + * This implementation returns the input value. + * + * @param value the value to check + * @throws UnsupportedOperationException if the map may not be changed by setValue + * @throws IllegalArgumentException if the specified value is invalid + * @throws ClassCastException if the class of the specified value is invalid + * @throws NullPointerException if the specified value is null and nulls are invalid + */ + protected abstract V checkSetValue(V value); + + /** + * Hook method called to determine if checkSetValue has any effect. + *

              + * An implementation should return false if the checkSetValue method + * has no effect as this optimises the implementation. + *

              + * This implementation returns true. + * + * @return true always + */ + protected boolean isSetValueChecking() { + return true; + } + + //----------------------------------------------------------------------- + @Override + public Set> entrySet() { + if (isSetValueChecking()) { + return new EntrySet(map.entrySet(), this); + } + return map.entrySet(); + } + + //----------------------------------------------------------------------- + /** + * Implementation of an entry set that checks additions via setValue. + */ + private class EntrySet extends AbstractSetDecorator> { + + /** Generated serial version ID. */ + private static final long serialVersionUID = 4354731610923110264L; + + /** The parent map */ + private final AbstractInputCheckedMapDecorator parent; + + protected EntrySet(final Set> set, final AbstractInputCheckedMapDecorator parent) { + super(set); + this.parent = parent; + } + + @Override + public Iterator> iterator() { + return new EntrySetIterator(this.decorated().iterator(), parent); + } + + @Override + @SuppressWarnings("unchecked") + public Object[] toArray() { + final Object[] array = this.decorated().toArray(); + for (int i = 0; i < array.length; i++) { + array[i] = new MapEntry((Map.Entry) array[i], parent); + } + return array; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(final T[] array) { + Object[] result = array; + if (array.length > 0) { + // we must create a new array to handle multi-threaded situations + // where another thread could access data before we decorate it + result = (Object[]) Array.newInstance(array.getClass().getComponentType(), 0); + } + result = this.decorated().toArray(result); + for (int i = 0; i < result.length; i++) { + result[i] = new MapEntry((Map.Entry) result[i], parent); + } + + // check to see if result should be returned straight + if (result.length > array.length) { + return (T[]) result; + } + + // copy back into input array to fulfil the method contract + System.arraycopy(result, 0, array, 0, result.length); + if (array.length > result.length) { + array[result.length] = null; + } + return array; + } + } + + /** + * Implementation of an entry set iterator that checks additions via setValue. + */ + private class EntrySetIterator extends AbstractIteratorDecorator> { + + /** The parent map */ + private final AbstractInputCheckedMapDecorator parent; + + protected EntrySetIterator(final Iterator> iterator, + final AbstractInputCheckedMapDecorator parent) { + super(iterator); + this.parent = parent; + } + + @Override + public Map.Entry next() { + final Map.Entry entry = getIterator().next(); + return new MapEntry(entry, parent); + } + } + + /** + * Implementation of a map entry that checks additions via setValue. + */ + private class MapEntry extends AbstractMapEntryDecorator { + + /** The parent map */ + private final AbstractInputCheckedMapDecorator parent; + + protected MapEntry(final Map.Entry entry, final AbstractInputCheckedMapDecorator parent) { + super(entry); + this.parent = parent; + } + + @Override + public V setValue(V value) { + value = parent.checkSetValue(value); + return getMapEntry().setValue(value); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractIterableMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractIterableMap.java new file mode 100644 index 000000000..27d712ab7 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractIterableMap.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import org.apache.commons.collections4.IterableMap; +import org.apache.commons.collections4.MapIterator; + +/** + * Provide a basic {@link IterableMap} implementation. + * + * @since 4.0 + * @version $Id: AbstractIterableMap.java 1469004 2013-04-17 17:37:03Z tn $ + */ +public abstract class AbstractIterableMap implements IterableMap { + + /** + * {@inheritDoc} + */ + public MapIterator mapIterator() { + return new EntrySetToMapIteratorAdapter(entrySet()); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractLinkedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractLinkedMap.java new file mode 100644 index 000000000..d7f515067 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractLinkedMap.java @@ -0,0 +1,605 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.OrderedIterator; +import org.apache.commons.collections4.OrderedMap; +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.ResettableIterator; +import org.apache.commons.collections4.iterators.EmptyOrderedIterator; +import org.apache.commons.collections4.iterators.EmptyOrderedMapIterator; + +/** + * An abstract implementation of a hash-based map that links entries to create an + * ordered map and which provides numerous points for subclasses to override. + *

              + * This class implements all the features necessary for a subclass linked + * hash-based map. Key-value entries are stored in instances of the + * LinkEntry class which can be overridden and replaced. + * The iterators can similarly be replaced, without the need to replace the KeySet, + * EntrySet and Values view classes. + *

              + * Overridable methods are provided to change the default hashing behaviour, and + * to change how entries are added to and removed from the map. Hopefully, all you + * need for unusual subclasses is here. + *

              + * This implementation maintains order by original insertion, but subclasses + * may work differently. The OrderedMap interface is implemented + * to provide access to bidirectional iteration and extra convenience methods. + *

              + * The orderedMapIterator() method provides direct access to a + * bidirectional iterator. The iterators from the other views can also be cast + * to OrderedIterator if required. + *

              + * All the available iterators can be reset back to the start by casting to + * ResettableIterator and calling reset(). + *

              + * The implementation is also designed to be subclassed, with lots of useful + * methods exposed. + * + * @since 3.0 + * @version $Id: AbstractLinkedMap.java 1533984 2013-10-20 21:12:51Z tn $ + */ +public abstract class AbstractLinkedMap extends AbstractHashedMap implements OrderedMap { + + /** Header in the linked list */ + transient LinkEntry header; + + /** + * Constructor only used in deserialization, do not use otherwise. + */ + protected AbstractLinkedMap() { + super(); + } + + /** + * Constructor which performs no validation on the passed in parameters. + * + * @param initialCapacity the initial capacity, must be a power of two + * @param loadFactor the load factor, must be > 0.0f and generally < 1.0f + * @param threshold the threshold, must be sensible + */ + protected AbstractLinkedMap(final int initialCapacity, final float loadFactor, final int threshold) { + super(initialCapacity, loadFactor, threshold); + } + + /** + * Constructs a new, empty map with the specified initial capacity. + * + * @param initialCapacity the initial capacity + * @throws IllegalArgumentException if the initial capacity is negative + */ + protected AbstractLinkedMap(final int initialCapacity) { + super(initialCapacity); + } + + /** + * Constructs a new, empty map with the specified initial capacity and + * load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @throws IllegalArgumentException if the initial capacity is negative + * @throws IllegalArgumentException if the load factor is less than zero + */ + protected AbstractLinkedMap(final int initialCapacity, final float loadFactor) { + super(initialCapacity, loadFactor); + } + + /** + * Constructor copying elements from another map. + * + * @param map the map to copy + * @throws NullPointerException if the map is null + */ + protected AbstractLinkedMap(final Map map) { + super(map); + } + + /** + * Initialise this subclass during construction. + *

              + * NOTE: As from v3.2 this method calls + * {@link #createEntry(HashEntry, int, Object, Object)} to create + * the map entry object. + */ + @Override + protected void init() { + header = createEntry(null, -1, null, null); + header.before = header.after = header; + } + + //----------------------------------------------------------------------- + /** + * Checks whether the map contains the specified value. + * + * @param value the value to search for + * @return true if the map contains the value + */ + @Override + public boolean containsValue(final Object value) { + // override uses faster iterator + if (value == null) { + for (LinkEntry entry = header.after; entry != header; entry = entry.after) { + if (entry.getValue() == null) { + return true; + } + } + } else { + for (LinkEntry entry = header.after; entry != header; entry = entry.after) { + if (isEqualValue(value, entry.getValue())) { + return true; + } + } + } + return false; + } + + /** + * Clears the map, resetting the size to zero and nullifying references + * to avoid garbage collection issues. + */ + @Override + public void clear() { + // override to reset the linked list + super.clear(); + header.before = header.after = header; + } + + //----------------------------------------------------------------------- + /** + * Gets the first key in the map, which is the first inserted. + * + * @return the eldest key + */ + public K firstKey() { + if (size == 0) { + throw new NoSuchElementException("Map is empty"); + } + return header.after.getKey(); + } + + /** + * Gets the last key in the map, which is the most recently inserted. + * + * @return the most recently inserted key + */ + public K lastKey() { + if (size == 0) { + throw new NoSuchElementException("Map is empty"); + } + return header.before.getKey(); + } + + /** + * Gets the next key in sequence. + * + * @param key the key to get after + * @return the next key + */ + public K nextKey(final Object key) { + final LinkEntry entry = getEntry(key); + return entry == null || entry.after == header ? null : entry.after.getKey(); + } + + @Override + protected LinkEntry getEntry(final Object key) { + return (LinkEntry) super.getEntry(key); + } + + /** + * Gets the previous key in sequence. + * + * @param key the key to get before + * @return the previous key + */ + public K previousKey(final Object key) { + final LinkEntry entry = getEntry(key); + return entry == null || entry.before == header ? null : entry.before.getKey(); + } + + //----------------------------------------------------------------------- + /** + * Gets the key at the specified index. + * + * @param index the index to retrieve + * @return the key at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + */ + protected LinkEntry getEntry(final int index) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index " + index + " is less than zero"); + } + if (index >= size) { + throw new IndexOutOfBoundsException("Index " + index + " is invalid for size " + size); + } + LinkEntry entry; + if (index < size / 2) { + // Search forwards + entry = header.after; + for (int currentIndex = 0; currentIndex < index; currentIndex++) { + entry = entry.after; + } + } else { + // Search backwards + entry = header; + for (int currentIndex = size; currentIndex > index; currentIndex--) { + entry = entry.before; + } + } + return entry; + } + + /** + * Adds an entry into this map, maintaining insertion order. + *

              + * This implementation adds the entry to the data storage table and + * to the end of the linked list. + * + * @param entry the entry to add + * @param hashIndex the index into the data array to store at + */ + @Override + protected void addEntry(final HashEntry entry, final int hashIndex) { + final LinkEntry link = (LinkEntry) entry; + link.after = header; + link.before = header.before; + header.before.after = link; + header.before = link; + data[hashIndex] = link; + } + + /** + * Creates an entry to store the data. + *

              + * This implementation creates a new LinkEntry instance. + * + * @param next the next entry in sequence + * @param hashCode the hash code to use + * @param key the key to store + * @param value the value to store + * @return the newly created entry + */ + @Override + protected LinkEntry createEntry(final HashEntry next, final int hashCode, final K key, final V value) { + return new LinkEntry(next, hashCode, convertKey(key), value); + } + + /** + * Removes an entry from the map and the linked list. + *

              + * This implementation removes the entry from the linked list chain, then + * calls the superclass implementation. + * + * @param entry the entry to remove + * @param hashIndex the index into the data structure + * @param previous the previous entry in the chain + */ + @Override + protected void removeEntry(final HashEntry entry, final int hashIndex, final HashEntry previous) { + final LinkEntry link = (LinkEntry) entry; + link.before.after = link.after; + link.after.before = link.before; + link.after = null; + link.before = null; + super.removeEntry(entry, hashIndex, previous); + } + + //----------------------------------------------------------------------- + /** + * Gets the before field from a LinkEntry. + * Used in subclasses that have no visibility of the field. + * + * @param entry the entry to query, must not be null + * @return the before field of the entry + * @throws NullPointerException if the entry is null + * @since 3.1 + */ + protected LinkEntry entryBefore(final LinkEntry entry) { + return entry.before; + } + + /** + * Gets the after field from a LinkEntry. + * Used in subclasses that have no visibility of the field. + * + * @param entry the entry to query, must not be null + * @return the after field of the entry + * @throws NullPointerException if the entry is null + * @since 3.1 + */ + protected LinkEntry entryAfter(final LinkEntry entry) { + return entry.after; + } + + //----------------------------------------------------------------------- + /** + * {@inheritDoc} + */ + @Override + public OrderedMapIterator mapIterator() { + if (size == 0) { + return EmptyOrderedMapIterator.emptyOrderedMapIterator(); + } + return new LinkMapIterator(this); + } + + /** + * MapIterator implementation. + */ + protected static class LinkMapIterator extends LinkIterator implements + OrderedMapIterator, ResettableIterator { + + protected LinkMapIterator(final AbstractLinkedMap parent) { + super(parent); + } + + public K next() { + return super.nextEntry().getKey(); + } + + public K previous() { + return super.previousEntry().getKey(); + } + + public K getKey() { + final LinkEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); + } + return current.getKey(); + } + + public V getValue() { + final LinkEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); + } + return current.getValue(); + } + + public V setValue(final V value) { + final LinkEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); + } + return current.setValue(value); + } + } + + //----------------------------------------------------------------------- + /** + * Creates an entry set iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the entrySet iterator + */ + @Override + protected Iterator> createEntrySetIterator() { + if (size() == 0) { + return EmptyOrderedIterator.>emptyOrderedIterator(); + } + return new EntrySetIterator(this); + } + + /** + * EntrySet iterator. + */ + protected static class EntrySetIterator extends LinkIterator implements + OrderedIterator>, ResettableIterator> { + + protected EntrySetIterator(final AbstractLinkedMap parent) { + super(parent); + } + + public Map.Entry next() { + return super.nextEntry(); + } + + public Map.Entry previous() { + return super.previousEntry(); + } + } + + //----------------------------------------------------------------------- + /** + * Creates a key set iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the keySet iterator + */ + @Override + protected Iterator createKeySetIterator() { + if (size() == 0) { + return EmptyOrderedIterator.emptyOrderedIterator(); + } + return new KeySetIterator(this); + } + + /** + * KeySet iterator. + */ + protected static class KeySetIterator extends LinkIterator implements + OrderedIterator, ResettableIterator { + + @SuppressWarnings("unchecked") + protected KeySetIterator(final AbstractLinkedMap parent) { + super((AbstractLinkedMap) parent); + } + + public K next() { + return super.nextEntry().getKey(); + } + + public K previous() { + return super.previousEntry().getKey(); + } + } + + //----------------------------------------------------------------------- + /** + * Creates a values iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the values iterator + */ + @Override + protected Iterator createValuesIterator() { + if (size() == 0) { + return EmptyOrderedIterator.emptyOrderedIterator(); + } + return new ValuesIterator(this); + } + + /** + * Values iterator. + */ + protected static class ValuesIterator extends LinkIterator implements + OrderedIterator, ResettableIterator { + + @SuppressWarnings("unchecked") + protected ValuesIterator(final AbstractLinkedMap parent) { + super((AbstractLinkedMap) parent); + } + + public V next() { + return super.nextEntry().getValue(); + } + + public V previous() { + return super.previousEntry().getValue(); + } + } + + //----------------------------------------------------------------------- + /** + * LinkEntry that stores the data. + *

              + * If you subclass AbstractLinkedMap but not LinkEntry + * then you will not be able to access the protected fields. + * The entryXxx() methods on AbstractLinkedMap exist + * to provide the necessary access. + */ + protected static class LinkEntry extends HashEntry { + /** The entry before this one in the order */ + protected LinkEntry before; + /** The entry after this one in the order */ + protected LinkEntry after; + + /** + * Constructs a new entry. + * + * @param next the next entry in the hash bucket sequence + * @param hashCode the hash code + * @param key the key + * @param value the value + */ + protected LinkEntry(final HashEntry next, final int hashCode, final Object key, final V value) { + super(next, hashCode, key, value); + } + } + + /** + * Base Iterator that iterates in link order. + */ + protected static abstract class LinkIterator { + + /** The parent map */ + protected final AbstractLinkedMap parent; + /** The current (last returned) entry */ + protected LinkEntry last; + /** The next entry */ + protected LinkEntry next; + /** The modification count expected */ + protected int expectedModCount; + + protected LinkIterator(final AbstractLinkedMap parent) { + super(); + this.parent = parent; + this.next = parent.header.after; + this.expectedModCount = parent.modCount; + } + + public boolean hasNext() { + return next != parent.header; + } + + public boolean hasPrevious() { + return next.before != parent.header; + } + + protected LinkEntry nextEntry() { + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + if (next == parent.header) { + throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); + } + last = next; + next = next.after; + return last; + } + + protected LinkEntry previousEntry() { + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + final LinkEntry previous = next.before; + if (previous == parent.header) { + throw new NoSuchElementException(AbstractHashedMap.NO_PREVIOUS_ENTRY); + } + next = previous; + last = previous; + return last; + } + + protected LinkEntry currentEntry() { + return last; + } + + public void remove() { + if (last == null) { + throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); + } + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + parent.remove(last.getKey()); + last = null; + expectedModCount = parent.modCount; + } + + public void reset() { + last = null; + next = parent.header.after; + } + + @Override + public String toString() { + if (last != null) { + return "Iterator[" + last.getKey() + "=" + last.getValue() + "]"; + } + return "Iterator[]"; + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractMapDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractMapDecorator.java new file mode 100644 index 000000000..6c48bdc3e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractMapDecorator.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Provides a base decorator that enables additional functionality to be added + * to a Map via decoration. + *

              + * Methods are forwarded directly to the decorated map. + *

              + * This implementation does not perform any special processing with + * {@link #entrySet()}, {@link #keySet()} or {@link #values()}. Instead + * it simply returns the set/collection from the wrapped map. This may be + * undesirable, for example if you are trying to write a validating + * implementation it would provide a loophole around the validation. + * But, you might want that loophole, so this class is kept simple. + * + * @param the type of the keys in the map + * @param the type of the values in the map + * @since 3.0 + * @version $Id: AbstractMapDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractMapDecorator extends AbstractIterableMap { + + /** The map to decorate */ + transient Map map; + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since 3.1 + */ + protected AbstractMapDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if the map is null + */ + protected AbstractMapDecorator(final Map map) { + if (map == null) { + throw new NullPointerException("Map must not be null."); + } + this.map = map; + } + + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + protected Map decorated() { + return map; + } + + //----------------------------------------------------------------------- + public void clear() { + decorated().clear(); + } + + public boolean containsKey(final Object key) { + return decorated().containsKey(key); + } + + public boolean containsValue(final Object value) { + return decorated().containsValue(value); + } + + public Set> entrySet() { + return decorated().entrySet(); + } + + public V get(final Object key) { + return decorated().get(key); + } + + public boolean isEmpty() { + return decorated().isEmpty(); + } + + public Set keySet() { + return decorated().keySet(); + } + + public V put(final K key, final V value) { + return decorated().put(key, value); + } + + public void putAll(final Map mapToCopy) { + decorated().putAll(mapToCopy); + } + + public V remove(final Object key) { + return decorated().remove(key); + } + + public int size() { + return decorated().size(); + } + + public Collection values() { + return decorated().values(); + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + return decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + @Override + public String toString() { + return decorated().toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractOrderedMapDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractOrderedMapDecorator.java new file mode 100644 index 000000000..7a0eaaf6a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractOrderedMapDecorator.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import org.apache.commons.collections4.OrderedMap; +import org.apache.commons.collections4.OrderedMapIterator; + +/** + * Provides a base decorator that enables additional functionality to be added + * to an OrderedMap via decoration. + *

              + * Methods are forwarded directly to the decorated map. + *

              + * This implementation does not perform any special processing with the map views. + * Instead it simply returns the set/collection from the wrapped map. This may be + * undesirable, for example if you are trying to write a validating implementation + * it would provide a loophole around the validation. + * But, you might want that loophole, so this class is kept simple. + * + * @since 3.0 + * @version $Id: AbstractOrderedMapDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractOrderedMapDecorator extends AbstractMapDecorator + implements OrderedMap { + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since 3.1 + */ + protected AbstractOrderedMapDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if the map is null + */ + public AbstractOrderedMapDecorator(final OrderedMap map) { + super(map); + } + + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + @Override + protected OrderedMap decorated() { + return (OrderedMap) super.decorated(); + } + + //----------------------------------------------------------------------- + public K firstKey() { + return decorated().firstKey(); + } + + public K lastKey() { + return decorated().lastKey(); + } + + public K nextKey(final K key) { + return decorated().nextKey(key); + } + + public K previousKey(final K key) { + return decorated().previousKey(key); + } + + @Override + public OrderedMapIterator mapIterator() { + return decorated().mapIterator(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractReferenceMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractReferenceMap.java new file mode 100644 index 000000000..1b091f66b --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractReferenceMap.java @@ -0,0 +1,1058 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.keyvalue.DefaultMapEntry; + +/** + * An abstract implementation of a hash-based map that allows the entries to + * be removed by the garbage collector. + *

              + * This class implements all the features necessary for a subclass reference + * hash-based map. Key-value entries are stored in instances of the + * ReferenceEntry class which can be overridden and replaced. + * The iterators can similarly be replaced, without the need to replace the KeySet, + * EntrySet and Values view classes. + *

              + * Overridable methods are provided to change the default hashing behaviour, and + * to change how entries are added to and removed from the map. Hopefully, all you + * need for unusual subclasses is here. + *

              + * When you construct an AbstractReferenceMap, you can specify what + * kind of references are used to store the map's keys and values. + * If non-hard references are used, then the garbage collector can remove + * mappings if a key or value becomes unreachable, or if the JVM's memory is + * running low. For information on how the different reference types behave, + * see {@link Reference}. + *

              + * Different types of references can be specified for keys and values. + * The keys can be configured to be weak but the values hard, + * in which case this class will behave like a + * + * WeakHashMap. However, you can also specify hard keys and + * weak values, or any other combination. The default constructor uses + * hard keys and soft values, providing a memory-sensitive cache. + *

              + * This {@link Map} implementation does not allow null elements. + * Attempting to add a null key or value to the map will raise a + * NullPointerException. + *

              + * All the available iterators can be reset back to the start by casting to + * ResettableIterator and calling reset(). + *

              + * This implementation is not synchronized. + * You can use {@link java.util.Collections#synchronizedMap} to + * provide synchronized access to a ReferenceMap. + * + * @see java.lang.ref.Reference + * @since 3.1 (extracted from ReferenceMap in 3.0) + * @version $Id: AbstractReferenceMap.java 1477799 2013-04-30 19:56:11Z tn $ + */ +public abstract class AbstractReferenceMap extends AbstractHashedMap { + + /** + * Reference type enum. + */ + public static enum ReferenceStrength { + HARD(0), SOFT(1), WEAK(2); + + /** value */ + public final int value; + + /** + * Resolve enum from int. + * @param value the int value + * @return ReferenceType + * @throws IllegalArgumentException if the specified value is invalid. + */ + public static ReferenceStrength resolve(final int value) { + switch (value) { + case 0: + return HARD; + case 1: + return SOFT; + case 2: + return WEAK; + default: + throw new IllegalArgumentException(); + } + } + + private ReferenceStrength(final int value) { + this.value = value; + } + + } + + /** + * The reference type for keys. + */ + private ReferenceStrength keyType; + + /** + * The reference type for values. + */ + private ReferenceStrength valueType; + + /** + * Should the value be automatically purged when the associated key has been collected? + */ + private boolean purgeValues; + + /** + * ReferenceQueue used to eliminate stale mappings. + * See purge. + */ + private transient ReferenceQueue queue; + + //----------------------------------------------------------------------- + /** + * Constructor used during deserialization. + */ + protected AbstractReferenceMap() { + super(); + } + + /** + * Constructs a new empty map with the specified reference types, + * load factor and initial capacity. + * + * @param keyType the type of reference to use for keys; + * must be {@link ReferenceStrength#HARD HARD}, + * {@link ReferenceStrength#SOFT SOFT}, + * {@link ReferenceStrength#WEAK WEAK} + * @param valueType the type of reference to use for values; + * must be {@link ReferenceStrength#HARD}, + * {@link ReferenceStrength#SOFT SOFT}, + * {@link ReferenceStrength#WEAK WEAK} + * @param capacity the initial capacity for the map + * @param loadFactor the load factor for the map + * @param purgeValues should the value be automatically purged when the + * key is garbage collected + */ + protected AbstractReferenceMap( + final ReferenceStrength keyType, final ReferenceStrength valueType, final int capacity, + final float loadFactor, final boolean purgeValues) { + super(capacity, loadFactor); + this.keyType = keyType; + this.valueType = valueType; + this.purgeValues = purgeValues; + } + + /** + * Initialise this subclass during construction, cloning or deserialization. + */ + @Override + protected void init() { + queue = new ReferenceQueue(); + } + + //----------------------------------------------------------------------- + /** + * Gets the size of the map. + * + * @return the size + */ + @Override + public int size() { + purgeBeforeRead(); + return super.size(); + } + + /** + * Checks whether the map is currently empty. + * + * @return true if the map is currently size zero + */ + @Override + public boolean isEmpty() { + purgeBeforeRead(); + return super.isEmpty(); + } + + /** + * Checks whether the map contains the specified key. + * + * @param key the key to search for + * @return true if the map contains the key + */ + @Override + public boolean containsKey(final Object key) { + purgeBeforeRead(); + final Entry entry = getEntry(key); + if (entry == null) { + return false; + } + return entry.getValue() != null; + } + + /** + * Checks whether the map contains the specified value. + * + * @param value the value to search for + * @return true if the map contains the value + */ + @Override + public boolean containsValue(final Object value) { + purgeBeforeRead(); + if (value == null) { + return false; + } + return super.containsValue(value); + } + + /** + * Gets the value mapped to the key specified. + * + * @param key the key + * @return the mapped value, null if no match + */ + @Override + public V get(final Object key) { + purgeBeforeRead(); + final Entry entry = getEntry(key); + if (entry == null) { + return null; + } + return entry.getValue(); + } + + + /** + * Puts a key-value mapping into this map. + * Neither the key nor the value may be null. + * + * @param key the key to add, must not be null + * @param value the value to add, must not be null + * @return the value previously mapped to this key, null if none + * @throws NullPointerException if either the key or value is null + */ + @Override + public V put(final K key, final V value) { + if (key == null) { + throw new NullPointerException("null keys not allowed"); + } + if (value == null) { + throw new NullPointerException("null values not allowed"); + } + + purgeBeforeWrite(); + return super.put(key, value); + } + + /** + * Removes the specified mapping from this map. + * + * @param key the mapping to remove + * @return the value mapped to the removed key, null if key not in map + */ + @Override + public V remove(final Object key) { + if (key == null) { + return null; + } + purgeBeforeWrite(); + return super.remove(key); + } + + /** + * Clears this map. + */ + @Override + public void clear() { + super.clear(); + while (queue.poll() != null) {} // drain the queue + } + + //----------------------------------------------------------------------- + /** + * Gets a MapIterator over the reference map. + * The iterator only returns valid key/value pairs. + * + * @return a map iterator + */ + @Override + public MapIterator mapIterator() { + return new ReferenceMapIterator(this); + } + + /** + * Returns a set view of this map's entries. + * An iterator returned entry is valid until next() is called again. + * The setValue() method on the toArray entries has no effect. + * + * @return a set view of this map's entries + */ + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = new ReferenceEntrySet(this); + } + return entrySet; + } + + /** + * Returns a set view of this map's keys. + * + * @return a set view of this map's keys + */ + @Override + public Set keySet() { + if (keySet == null) { + keySet = new ReferenceKeySet(this); + } + return keySet; + } + + /** + * Returns a collection view of this map's values. + * + * @return a set view of this map's values + */ + @Override + public Collection values() { + if (values == null) { + values = new ReferenceValues(this); + } + return values; + } + + //----------------------------------------------------------------------- + /** + * Purges stale mappings from this map before read operations. + *

              + * This implementation calls {@link #purge()} to maintain a consistent state. + */ + protected void purgeBeforeRead() { + purge(); + } + + /** + * Purges stale mappings from this map before write operations. + *

              + * This implementation calls {@link #purge()} to maintain a consistent state. + */ + protected void purgeBeforeWrite() { + purge(); + } + + /** + * Purges stale mappings from this map. + *

              + * Note that this method is not synchronized! Special + * care must be taken if, for instance, you want stale + * mappings to be removed on a periodic basis by some + * background thread. + */ + protected void purge() { + Reference ref = queue.poll(); + while (ref != null) { + purge(ref); + ref = queue.poll(); + } + } + + /** + * Purges the specified reference. + * + * @param ref the reference to purge + */ + protected void purge(final Reference ref) { + // The hashCode of the reference is the hashCode of the + // mapping key, even if the reference refers to the + // mapping value... + final int hash = ref.hashCode(); + final int index = hashIndex(hash, data.length); + HashEntry previous = null; + HashEntry entry = data[index]; + while (entry != null) { + if (((ReferenceEntry) entry).purge(ref)) { + if (previous == null) { + data[index] = entry.next; + } else { + previous.next = entry.next; + } + this.size--; + return; + } + previous = entry; + entry = entry.next; + } + + } + + //----------------------------------------------------------------------- + /** + * Gets the entry mapped to the key specified. + * + * @param key the key + * @return the entry, null if no match + */ + @Override + protected HashEntry getEntry(final Object key) { + if (key == null) { + return null; + } + return super.getEntry(key); + } + + /** + * Gets the hash code for a MapEntry. + * Subclasses can override this, for example to use the identityHashCode. + * + * @param key the key to get a hash code for, may be null + * @param value the value to get a hash code for, may be null + * @return the hash code, as per the MapEntry specification + */ + protected int hashEntry(final Object key, final Object value) { + return (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()); + } + + /** + * Compares two keys, in internal converted form, to see if they are equal. + *

              + * This implementation converts the key from the entry to a real reference + * before comparison. + * + * @param key1 the first key to compare passed in from outside + * @param key2 the second key extracted from the entry via entry.key + * @return true if equal + */ + @Override + @SuppressWarnings("unchecked") + protected boolean isEqualKey(final Object key1, Object key2) { + key2 = keyType == ReferenceStrength.HARD ? key2 : ((Reference) key2).get(); + return key1 == key2 || key1.equals(key2); + } + + /** + * Creates a ReferenceEntry instead of a HashEntry. + * + * @param next the next entry in sequence + * @param hashCode the hash code to use + * @param key the key to store + * @param value the value to store + * @return the newly created entry + */ + @Override + protected ReferenceEntry createEntry(final HashEntry next, final int hashCode, + final K key, final V value) { + return new ReferenceEntry(this, next, hashCode, key, value); + } + + /** + * Creates an entry set iterator. + * + * @return the entrySet iterator + */ + @Override + protected Iterator> createEntrySetIterator() { + return new ReferenceEntrySetIterator(this); + } + + /** + * Creates an key set iterator. + * + * @return the keySet iterator + */ + @Override + protected Iterator createKeySetIterator() { + return new ReferenceKeySetIterator(this); + } + + /** + * Creates an values iterator. + * + * @return the values iterator + */ + @Override + protected Iterator createValuesIterator() { + return new ReferenceValuesIterator(this); + } + + //----------------------------------------------------------------------- + /** + * EntrySet implementation. + */ + static class ReferenceEntrySet extends EntrySet { + + protected ReferenceEntrySet(final AbstractHashedMap parent) { + super(parent); + } + + @Override + public Object[] toArray() { + return toArray(new Object[size()]); + } + + @Override + public T[] toArray(final T[] arr) { + // special implementation to handle disappearing entries + final ArrayList> list = new ArrayList>(size()); + for (final Map.Entry entry : this) { + list.add(new DefaultMapEntry(entry)); + } + return list.toArray(arr); + } + } + + //----------------------------------------------------------------------- + /** + * KeySet implementation. + */ + static class ReferenceKeySet extends KeySet { + + protected ReferenceKeySet(final AbstractHashedMap parent) { + super(parent); + } + + @Override + public Object[] toArray() { + return toArray(new Object[size()]); + } + + @Override + public T[] toArray(final T[] arr) { + // special implementation to handle disappearing keys + final List list = new ArrayList(size()); + for (final K key : this) { + list.add(key); + } + return list.toArray(arr); + } + } + + //----------------------------------------------------------------------- + /** + * Values implementation. + */ + static class ReferenceValues extends Values { + + protected ReferenceValues(final AbstractHashedMap parent) { + super(parent); + } + + @Override + public Object[] toArray() { + return toArray(new Object[size()]); + } + + @Override + public T[] toArray(final T[] arr) { + // special implementation to handle disappearing values + final List list = new ArrayList(size()); + for (final V value : this) { + list.add(value); + } + return list.toArray(arr); + } + } + + //----------------------------------------------------------------------- + /** + * A MapEntry implementation for the map. + *

              + * If getKey() or getValue() returns null, it means + * the mapping is stale and should be removed. + * + * @since 3.1 + */ + protected static class ReferenceEntry extends HashEntry { + /** The parent map */ + private final AbstractReferenceMap parent; + + /** + * Creates a new entry object for the ReferenceMap. + * + * @param parent the parent map + * @param next the next entry in the hash bucket + * @param hashCode the hash code of the key + * @param key the key + * @param value the value + */ + public ReferenceEntry(final AbstractReferenceMap parent, final HashEntry next, + final int hashCode, final K key, final V value) { + super(next, hashCode, null, null); + this.parent = parent; + this.key = toReference(parent.keyType, key, hashCode); + this.value = toReference(parent.valueType, value, hashCode); // the key hashCode is passed in deliberately + } + + /** + * Gets the key from the entry. + * This method dereferences weak and soft keys and thus may return null. + * + * @return the key, which may be null if it was garbage collected + */ + @Override + @SuppressWarnings("unchecked") + public K getKey() { + return (K) (parent.keyType == ReferenceStrength.HARD ? key : ((Reference) key).get()); + } + + /** + * Gets the value from the entry. + * This method dereferences weak and soft value and thus may return null. + * + * @return the value, which may be null if it was garbage collected + */ + @Override + @SuppressWarnings("unchecked") + public V getValue() { + return (V) (parent.valueType == ReferenceStrength.HARD ? value : ((Reference) value).get()); + } + + /** + * Sets the value of the entry. + * + * @param obj the object to store + * @return the previous value + */ + @Override + @SuppressWarnings("unchecked") + public V setValue(final V obj) { + final V old = getValue(); + if (parent.valueType != ReferenceStrength.HARD) { + ((Reference) value).clear(); + } + value = toReference(parent.valueType, obj, hashCode); + return old; + } + + /** + * Compares this map entry to another. + *

              + * This implementation uses isEqualKey and + * isEqualValue on the main map for comparison. + * + * @param obj the other map entry to compare to + * @return true if equal, false if not + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map.Entry == false) { + return false; + } + + final Map.Entry entry = (Map.Entry)obj; + final Object entryKey = entry.getKey(); // convert to hard reference + final Object entryValue = entry.getValue(); // convert to hard reference + if (entryKey == null || entryValue == null) { + return false; + } + // compare using map methods, aiding identity subclass + // note that key is direct access and value is via method + return parent.isEqualKey(entryKey, key) && + parent.isEqualValue(entryValue, getValue()); + } + + /** + * Gets the hashcode of the entry using temporary hard references. + *

              + * This implementation uses hashEntry on the main map. + * + * @return the hashcode of the entry + */ + @Override + public int hashCode() { + return parent.hashEntry(getKey(), getValue()); + } + + /** + * Constructs a reference of the given type to the given referent. + * The reference is registered with the queue for later purging. + * + * @param the type of the referenced object + * @param type HARD, SOFT or WEAK + * @param referent the object to refer to + * @param hash the hash code of the key of the mapping; + * this number might be different from referent.hashCode() if + * the referent represents a value and not a key + * @return the reference to the object + */ + protected Object toReference(final ReferenceStrength type, final T referent, final int hash) { + if (type == ReferenceStrength.HARD) { + return referent; + } + if (type == ReferenceStrength.SOFT) { + return new SoftRef(hash, referent, parent.queue); + } + if (type == ReferenceStrength.WEAK) { + return new WeakRef(hash, referent, parent.queue); + } + throw new Error(); + } + + /** + * Purges the specified reference + * @param ref the reference to purge + * @return true or false + */ + boolean purge(final Reference ref) { + boolean r = parent.keyType != ReferenceStrength.HARD && key == ref; + r = r || parent.valueType != ReferenceStrength.HARD && value == ref; + if (r) { + if (parent.keyType != ReferenceStrength.HARD) { + ((Reference) key).clear(); + } + if (parent.valueType != ReferenceStrength.HARD) { + ((Reference) value).clear(); + } else if (parent.purgeValues) { + value = null; + } + } + return r; + } + + /** + * Gets the next entry in the bucket. + * + * @return the next entry in the bucket + */ + protected ReferenceEntry next() { + return (ReferenceEntry) next; + } + } + + //----------------------------------------------------------------------- + /** + * Base iterator class. + */ + static class ReferenceBaseIterator { + /** The parent map */ + final AbstractReferenceMap parent; + + // These fields keep track of where we are in the table. + int index; + ReferenceEntry entry; + ReferenceEntry previous; + + // These Object fields provide hard references to the + // current and next entry; this assures that if hasNext() + // returns true, next() will actually return a valid element. + K currentKey, nextKey; + V currentValue, nextValue; + + int expectedModCount; + + public ReferenceBaseIterator(final AbstractReferenceMap parent) { + super(); + this.parent = parent; + index = parent.size() != 0 ? parent.data.length : 0; + // have to do this here! size() invocation above + // may have altered the modCount. + expectedModCount = parent.modCount; + } + + public boolean hasNext() { + checkMod(); + while (nextNull()) { + ReferenceEntry e = entry; + int i = index; + while (e == null && i > 0) { + i--; + e = (ReferenceEntry) parent.data[i]; + } + entry = e; + index = i; + if (e == null) { + currentKey = null; + currentValue = null; + return false; + } + nextKey = e.getKey(); + nextValue = e.getValue(); + if (nextNull()) { + entry = entry.next(); + } + } + return true; + } + + private void checkMod() { + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + private boolean nextNull() { + return nextKey == null || nextValue == null; + } + + protected ReferenceEntry nextEntry() { + checkMod(); + if (nextNull() && !hasNext()) { + throw new NoSuchElementException(); + } + previous = entry; + entry = entry.next(); + currentKey = nextKey; + currentValue = nextValue; + nextKey = null; + nextValue = null; + return previous; + } + + protected ReferenceEntry currentEntry() { + checkMod(); + return previous; + } + + public void remove() { + checkMod(); + if (previous == null) { + throw new IllegalStateException(); + } + parent.remove(currentKey); + previous = null; + currentKey = null; + currentValue = null; + expectedModCount = parent.modCount; + } + } + + /** + * The EntrySet iterator. + */ + static class ReferenceEntrySetIterator + extends ReferenceBaseIterator implements Iterator> { + + public ReferenceEntrySetIterator(final AbstractReferenceMap parent) { + super(parent); + } + + public Map.Entry next() { + return nextEntry(); + } + + } + + /** + * The keySet iterator. + */ + static class ReferenceKeySetIterator extends ReferenceBaseIterator implements Iterator { + + @SuppressWarnings("unchecked") + ReferenceKeySetIterator(final AbstractReferenceMap parent) { + super((AbstractReferenceMap) parent); + } + + public K next() { + return nextEntry().getKey(); + } + } + + /** + * The values iterator. + */ + static class ReferenceValuesIterator extends ReferenceBaseIterator implements Iterator { + + @SuppressWarnings("unchecked") + ReferenceValuesIterator(final AbstractReferenceMap parent) { + super((AbstractReferenceMap) parent); + } + + public V next() { + return nextEntry().getValue(); + } + } + + /** + * The MapIterator implementation. + */ + static class ReferenceMapIterator extends ReferenceBaseIterator implements MapIterator { + + protected ReferenceMapIterator(final AbstractReferenceMap parent) { + super(parent); + } + + public K next() { + return nextEntry().getKey(); + } + + public K getKey() { + final HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); + } + return current.getKey(); + } + + public V getValue() { + final HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); + } + return current.getValue(); + } + + public V setValue(final V value) { + final HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); + } + return current.setValue(value); + } + } + + //----------------------------------------------------------------------- + // These two classes store the hashCode of the key of + // of the mapping, so that after they're dequeued a quick + // lookup of the bucket in the table can occur. + + /** + * A soft reference holder. + */ + static class SoftRef extends SoftReference { + /** the hashCode of the key (even if the reference points to a value) */ + private final int hash; + + public SoftRef(final int hash, final T r, final ReferenceQueue q) { + super(r, q); + this.hash = hash; + } + + @Override + public int hashCode() { + return hash; + } + } + + /** + * A weak reference holder. + */ + static class WeakRef extends WeakReference { + /** the hashCode of the key (even if the reference points to a value) */ + private final int hash; + + public WeakRef(final int hash, final T r, final ReferenceQueue q) { + super(r, q); + this.hash = hash; + } + + @Override + public int hashCode() { + return hash; + } + } + + //----------------------------------------------------------------------- + /** + * Replaces the superclass method to store the state of this class. + *

              + * Serialization is not one of the JDK's nicest topics. Normal serialization will + * initialise the superclass before the subclass. Sometimes however, this isn't + * what you want, as in this case the put() method on read can be + * affected by subclass state. + *

              + * The solution adopted here is to serialize the state data of this class in + * this protected method. This method must be called by the + * writeObject() of the first serializable subclass. + *

              + * Subclasses may override if they have a specific field that must be present + * on read before this implementation will work. Generally, the read determines + * what must be serialized here, if anything. + * + * @param out the output stream + * @throws IOException if an error occurs while writing to the stream + */ + @Override + protected void doWriteObject(final ObjectOutputStream out) throws IOException { + out.writeInt(keyType.value); + out.writeInt(valueType.value); + out.writeBoolean(purgeValues); + out.writeFloat(loadFactor); + out.writeInt(data.length); + for (final MapIterator it = mapIterator(); it.hasNext();) { + out.writeObject(it.next()); + out.writeObject(it.getValue()); + } + out.writeObject(null); // null terminate map + // do not call super.doWriteObject() as code there doesn't work for reference map + } + + /** + * Replaces the superclass method to read the state of this class. + *

              + * Serialization is not one of the JDK's nicest topics. Normal serialization will + * initialise the superclass before the subclass. Sometimes however, this isn't + * what you want, as in this case the put() method on read can be + * affected by subclass state. + *

              + * The solution adopted here is to deserialize the state data of this class in + * this protected method. This method must be called by the + * readObject() of the first serializable subclass. + *

              + * Subclasses may override if the subclass has a specific field that must be present + * before put() or calculateThreshold() will work correctly. + * + * @param in the input stream + * @throws IOException if an error occurs while reading from the stream + * @throws ClassNotFoundException if an object read from the stream can not be loaded + */ + @Override + @SuppressWarnings("unchecked") + protected void doReadObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + this.keyType = ReferenceStrength.resolve(in.readInt()); + this.valueType = ReferenceStrength.resolve(in.readInt()); + this.purgeValues = in.readBoolean(); + this.loadFactor = in.readFloat(); + final int capacity = in.readInt(); + init(); + data = new HashEntry[capacity]; + while (true) { + final K key = (K) in.readObject(); + if (key == null) { + break; + } + final V value = (V) in.readObject(); + put(key, value); + } + threshold = calculateThreshold(data.length, loadFactor); + // do not call super.doReadObject() as code there doesn't work for reference map + } + + /** + * Provided protected read-only access to the key type. + * @param type the type to check against. + * @return true if keyType has the specified type + */ + protected boolean isKeyType(ReferenceStrength type) { + return this.keyType == type; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractSortedMapDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractSortedMapDecorator.java new file mode 100644 index 000000000..1a63559d9 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/AbstractSortedMapDecorator.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; + +import org.apache.commons.collections4.IterableSortedMap; +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.iterators.ListIteratorWrapper; + +/** + * Provides a base decorator that enables additional functionality to be added + * to a Map via decoration. + *

              + * Methods are forwarded directly to the decorated map. + *

              + * This implementation does not perform any special processing with the map views. + * Instead it simply returns the set/collection from the wrapped map. This may be + * undesirable, for example if you are trying to write a validating implementation + * it would provide a loophole around the validation. + * But, you might want that loophole, so this class is kept simple. + * + * @param the type of the keys in the map + * @param the type of the values in the map + * @since 3.0 + * @version $Id: AbstractSortedMapDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractSortedMapDecorator extends AbstractMapDecorator implements + IterableSortedMap { + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since 3.1 + */ + protected AbstractSortedMapDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if the map is null + */ + public AbstractSortedMapDecorator(final SortedMap map) { + super(map); + } + + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + @Override + protected SortedMap decorated() { + return (SortedMap) super.decorated(); + } + + //----------------------------------------------------------------------- + public Comparator comparator() { + return decorated().comparator(); + } + + public K firstKey() { + return decorated().firstKey(); + } + + public K lastKey() { + return decorated().lastKey(); + } + + public SortedMap subMap(final K fromKey, final K toKey) { + return decorated().subMap(fromKey, toKey); + } + + public SortedMap headMap(final K toKey) { + return decorated().headMap(toKey); + } + + public SortedMap tailMap(final K fromKey) { + return decorated().tailMap(fromKey); + } + + public K previousKey(final K key) { + final SortedMap headMap = headMap(key); + return headMap.isEmpty() ? null : headMap.lastKey(); + } + + public K nextKey(final K key) { + final Iterator it = tailMap(key).keySet().iterator(); + it.next(); + return it.hasNext() ? it.next() : null; + } + + /** + * {@inheritDoc} + */ + @Override + public OrderedMapIterator mapIterator() { + return new SortedMapIterator(entrySet()); + } + + /** + * OrderedMapIterator implementation. + * + * @param the key type + * @param the value type + */ + protected static class SortedMapIterator extends EntrySetToMapIteratorAdapter + implements OrderedMapIterator { + + /** + * Create a new AbstractSortedMapDecorator.SortedMapIterator. + * @param entrySet the entrySet to iterate + */ + protected SortedMapIterator(final Set> entrySet) { + super(entrySet); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void reset() { + super.reset(); + iterator = new ListIteratorWrapper>(iterator); + } + + /** + * {@inheritDoc} + */ + public boolean hasPrevious() { + return ((ListIterator>) iterator).hasPrevious(); + } + + /** + * {@inheritDoc} + */ + public K previous() { + entry = ((ListIterator>) iterator).previous(); + return getKey(); + } + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/CaseInsensitiveMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/CaseInsensitiveMap.java new file mode 100644 index 000000000..5489c08cf --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/CaseInsensitiveMap.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Map; + +/** + * A case-insensitive Map. + *

              + * Before keys are added to the map or compared to other existing keys, they are converted + * to all lowercase in a locale-independent fashion by using information from the Unicode + * data file. + *

              + * Null keys are supported. + *

              + * The keySet() method returns all lowercase keys, or nulls. + *

              + * Example: + *

              
              + *  Map<String, String> map = new CaseInsensitiveMap<String, String>();
              + *  map.put("One", "One");
              + *  map.put("Two", "Two");
              + *  map.put(null, "Three");
              + *  map.put("one", "Four");
              + * 
              + * creates a CaseInsensitiveMap with three entries.
              + * map.get(null) returns "Three" and map.get("ONE") + * returns "Four". The Set returned by keySet() + * equals {"one", "two", null}. + *

              + * This map will violate the detail of various Map and map view contracts. + * As a general rule, don't compare this map to other maps. In particular, you can't + * use decorators like {@link ListOrderedMap} on it, which silently assume that these + * contracts are fulfilled. + *

              + * Note that CaseInsensitiveMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + * + * @since 3.0 + * @version $Id: CaseInsensitiveMap.java 1533984 2013-10-20 21:12:51Z tn $ + */ +public class CaseInsensitiveMap extends AbstractHashedMap implements Serializable, Cloneable { + + /** Serialisation version */ + private static final long serialVersionUID = -7074655917369299456L; + + /** + * Constructs a new empty map with default size and load factor. + */ + public CaseInsensitiveMap() { + super(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_THRESHOLD); + } + + /** + * Constructs a new, empty map with the specified initial capacity. + * + * @param initialCapacity the initial capacity + * @throws IllegalArgumentException if the initial capacity is negative + */ + public CaseInsensitiveMap(final int initialCapacity) { + super(initialCapacity); + } + + /** + * Constructs a new, empty map with the specified initial capacity and + * load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @throws IllegalArgumentException if the initial capacity is negative + * @throws IllegalArgumentException if the load factor is less than zero + */ + public CaseInsensitiveMap(final int initialCapacity, final float loadFactor) { + super(initialCapacity, loadFactor); + } + + /** + * Constructor copying elements from another map. + *

              + * Keys will be converted to lower case strings, which may cause + * some entries to be removed (if string representation of keys differ + * only by character case). + * + * @param map the map to copy + * @throws NullPointerException if the map is null + */ + public CaseInsensitiveMap(final Map map) { + super(map); + } + + //----------------------------------------------------------------------- + /** + * Overrides convertKey() from {@link AbstractHashedMap} to convert keys to + * lower case. + *

              + * Returns {@link AbstractHashedMap#NULL} if key is null. + * + * @param key the key convert + * @return the converted key + */ + @Override + protected Object convertKey(final Object key) { + if (key != null) { + final char[] chars = key.toString().toCharArray(); + for (int i = chars.length - 1; i >= 0; i--) { + chars[i] = Character.toLowerCase(Character.toUpperCase(chars[i])); + } + return new String(chars); + } + return AbstractHashedMap.NULL; + } + + //----------------------------------------------------------------------- + /** + * Clones the map without cloning the keys or values. + * + * @return a shallow clone + */ + @Override + public CaseInsensitiveMap clone() { + return (CaseInsensitiveMap) super.clone(); + } + + /** + * Write the map out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + + /** + * Read the map in using a custom routine. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + doReadObject(in); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/CompositeMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/CompositeMap.java new file mode 100644 index 000000000..dcaa05d42 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/CompositeMap.java @@ -0,0 +1,548 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.Serializable; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.set.CompositeSet; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.collection.CompositeCollection; + +/** + * Decorates a map of other maps to provide a single unified view. + *

              + * Changes made to this map will actually be made on the decorated map. + * Add and remove operations require the use of a pluggable strategy. If no + * strategy is provided then add and remove are unsupported. + *

              + * Note that CompositeMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + * + * @since 3.0 + * @version $Id: CompositeMap.java 1612021 2014-07-20 04:51:05Z ggregory $ + */ +public class CompositeMap extends AbstractIterableMap implements Serializable { + + /** Serialization version */ + private static final long serialVersionUID = -6096931280583808322L; + + /** Array of all maps in the composite */ + private Map[] composite; + + /** Handle mutation operations */ + private MapMutator mutator; + + /** + * Create a new, empty, CompositeMap. + */ + @SuppressWarnings("unchecked") + public CompositeMap() { + this(new Map[] {}, null); + } + + /** + * Create a new CompositeMap with two composited Map instances. + * + * @param one the first Map to be composited + * @param two the second Map to be composited + * @throws IllegalArgumentException if there is a key collision + */ + @SuppressWarnings("unchecked") + public CompositeMap(final Map one, final Map two) { + this(new Map[] { one, two }, null); + } + + /** + * Create a new CompositeMap with two composited Map instances. + * + * @param one the first Map to be composited + * @param two the second Map to be composited + * @param mutator MapMutator to be used for mutation operations + */ + @SuppressWarnings("unchecked") + public CompositeMap(final Map one, final Map two, final MapMutator mutator) { + this(new Map[] { one, two }, mutator); + } + + /** + * Create a new CompositeMap which composites all of the Map instances in the + * argument. It copies the argument array, it does not use it directly. + * + * @param composite the Maps to be composited + * @throws IllegalArgumentException if there is a key collision + */ + public CompositeMap(final Map... composite) { + this(composite, null); + } + + /** + * Create a new CompositeMap which composites all of the Map instances in the + * argument. It copies the argument array, it does not use it directly. + * + * @param composite Maps to be composited + * @param mutator MapMutator to be used for mutation operations + */ + @SuppressWarnings("unchecked") + public CompositeMap(final Map[] composite, final MapMutator mutator) { + this.mutator = mutator; + this.composite = new Map[0]; + for (int i = composite.length - 1; i >= 0; --i) { + this.addComposited(composite[i]); + } + } + + //----------------------------------------------------------------------- + /** + * Specify the MapMutator to be used by mutation operations. + * + * @param mutator the MapMutator to be used for mutation delegation + */ + public void setMutator(final MapMutator mutator) { + this.mutator = mutator; + } + + /** + * Add an additional Map to the composite. + * + * @param map the Map to be added to the composite + * @throws IllegalArgumentException if there is a key collision and there is no + * MapMutator set to handle it. + */ + @SuppressWarnings("unchecked") + public synchronized void addComposited(final Map map) throws IllegalArgumentException { + for (int i = composite.length - 1; i >= 0; --i) { + final Collection intersect = CollectionUtils.intersection(this.composite[i].keySet(), map.keySet()); + if (intersect.size() != 0) { + if (this.mutator == null) { + throw new IllegalArgumentException("Key collision adding Map to CompositeMap"); + } + this.mutator.resolveCollision(this, this.composite[i], map, intersect); + } + } + final Map[] temp = new Map[this.composite.length + 1]; + System.arraycopy(this.composite, 0, temp, 0, this.composite.length); + temp[temp.length - 1] = map; + this.composite = temp; + } + + /** + * Remove a Map from the composite. + * + * @param map the Map to be removed from the composite + * @return The removed Map or null if map is not in the composite + */ + @SuppressWarnings("unchecked") + public synchronized Map removeComposited(final Map map) { + final int size = this.composite.length; + for (int i = 0; i < size; ++i) { + if (this.composite[i].equals(map)) { + final Map[] temp = new Map[size - 1]; + System.arraycopy(this.composite, 0, temp, 0, i); + System.arraycopy(this.composite, i + 1, temp, i, size - i - 1); + this.composite = temp; + return map; + } + } + return null; + } + + //----------------------------------------------------------------------- + /** + * Calls clear() on all composited Maps. + * + * @throws UnsupportedOperationException if any of the composited Maps do not support clear() + */ + public void clear() { + for (int i = this.composite.length - 1; i >= 0; --i) { + this.composite[i].clear(); + } + } + + /** + * Returns {@code true} if this map contains a mapping for the specified + * key. More formally, returns {@code true} if and only if + * this map contains at a mapping for a key {@code k} such that + * {@code (key==null ? k==null : key.equals(k))}. (There can be + * at most one such mapping.) + * + * @param key key whose presence in this map is to be tested. + * @return {@code true} if this map contains a mapping for the specified + * key. + * + * @throws ClassCastException if the key is of an inappropriate type for + * this map (optional). + * @throws NullPointerException if the key is {@code null} and this map + * does not not permit {@code null} keys (optional). + */ + public boolean containsKey(final Object key) { + for (int i = this.composite.length - 1; i >= 0; --i) { + if (this.composite[i].containsKey(key)) { + return true; + } + } + return false; + } + + /** + * Returns {@code true} if this map maps one or more keys to the + * specified value. More formally, returns {@code true} if and only if + * this map contains at least one mapping to a value {@code v} such that + * {@code (value==null ? v==null : value.equals(v))}. This operation + * will probably require time linear in the map size for most + * implementations of the {@code Map} interface. + * + * @param value value whose presence in this map is to be tested. + * @return {@code true} if this map maps one or more keys to the + * specified value. + * @throws ClassCastException if the value is of an inappropriate type for + * this map (optional). + * @throws NullPointerException if the value is {@code null} and this map + * does not not permit {@code null} values (optional). + */ + public boolean containsValue(final Object value) { + for (int i = this.composite.length - 1; i >= 0; --i) { + if (this.composite[i].containsValue(value)) { + return true; + } + } + return false; + } + + /** + * Returns a set view of the mappings contained in this map. Each element + * in the returned set is a Map.Entry. The set is backed by the + * map, so changes to the map are reflected in the set, and vice-versa. + * If the map is modified while an iteration over the set is in progress, + * the results of the iteration are undefined. The set supports element + * removal, which removes the corresponding mapping from the map, via the + * {@code Iterator.remove}, {@code Set.remove}, {@code removeAll}, + * {@code retainAll} and {@code clear} operations. It does not support + * the {@code add} or {@code addAll} operations. + *

              + * This implementation returns a CompositeSet which + * composites the entry sets from all of the composited maps. + * + * @see CompositeSet + * @return a set view of the mappings contained in this map. + */ + public Set> entrySet() { + final CompositeSet> entries = new CompositeSet>(); + for (int i = composite.length - 1; i >= 0; --i) { + entries.addComposited(composite[i].entrySet()); + } + return entries; + } + + /** + * Returns the value to which this map maps the specified key. Returns + * {@code null} if the map contains no mapping for this key. A return + * value of {@code null} does not necessarily indicate that the + * map contains no mapping for the key; it's also possible that the map + * explicitly maps the key to {@code null}. The {@code containsKey} + * operation may be used to distinguish these two cases. + * + *

              More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that (key==null ? k==null : + * key.equals(k)), then this method returns {@code v}; otherwise + * it returns {@code null}. (There can be at most one such mapping.) + * + * @param key key whose associated value is to be returned. + * @return the value to which this map maps the specified key, or + * {@code null} if the map contains no mapping for this key. + * + * @throws ClassCastException if the key is of an inappropriate type for + * this map (optional). + * @throws NullPointerException key is {@code null} and this map does not + * not permit {@code null} keys (optional). + * + * @see #containsKey(Object) + */ + public V get(final Object key) { + for (int i = this.composite.length - 1; i >= 0; --i) { + if (this.composite[i].containsKey(key)) { + return this.composite[i].get(key); + } + } + return null; + } + + /** + * Returns {@code true} if this map contains no key-value mappings. + * + * @return {@code true} if this map contains no key-value mappings. + */ + public boolean isEmpty() { + for (int i = this.composite.length - 1; i >= 0; --i) { + if (!this.composite[i].isEmpty()) { + return false; + } + } + return true; + } + + /** + * Returns a set view of the keys contained in this map. The set is + * backed by the map, so changes to the map are reflected in the set, and + * vice-versa. If the map is modified while an iteration over the set is + * in progress, the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding mapping from + * the map, via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll} {@code retainAll}, and {@code clear} operations. + * It does not support the add or {@code addAll} operations. + *

              + * This implementation returns a CompositeSet which + * composites the key sets from all of the composited maps. + * + * @return a set view of the keys contained in this map. + */ + public Set keySet() { + final CompositeSet keys = new CompositeSet(); + for (int i = this.composite.length - 1; i >= 0; --i) { + keys.addComposited(this.composite[i].keySet()); + } + return keys; + } + + /** + * Associates the specified value with the specified key in this map + * (optional operation). If the map previously contained a mapping for + * this key, the old value is replaced by the specified value. (A map + * {@code m} is said to contain a mapping for a key {@code k} if and only + * if {@link #containsKey(Object) m.containsKey(k)} would return + * {@code true}.)) + * + * @param key key with which the specified value is to be associated. + * @param value value to be associated with the specified key. + * @return previous value associated with specified key, or {@code null} + * if there was no mapping for key. A {@code null} return can + * also indicate that the map previously associated {@code null} + * with the specified key, if the implementation supports + * {@code null} values. + * + * @throws UnsupportedOperationException if no MapMutator has been specified + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map. + * @throws IllegalArgumentException if some aspect of this key or value + * prevents it from being stored in this map. + * @throws NullPointerException this map does not permit {@code null} + * keys or values, and the specified key or value is + * {@code null}. + */ + public V put(final K key, final V value) { + if (this.mutator == null) { + throw new UnsupportedOperationException("No mutator specified"); + } + return this.mutator.put(this, this.composite, key, value); + } + + /** + * Copies all of the mappings from the specified map to this map + * (optional operation). The effect of this call is equivalent to that + * of calling {@link #put(Object,Object) put(k, v)} on this map once + * for each mapping from key {@code k} to value {@code v} in the + * specified map. The behavior of this operation is unspecified if the + * specified map is modified while the operation is in progress. + * + * @param map Mappings to be stored in this map. + * + * @throws UnsupportedOperationException if the {@code putAll} method is + * not supported by this map. + * + * @throws ClassCastException if the class of a key or value in the + * specified map prevents it from being stored in this map. + * + * @throws IllegalArgumentException some aspect of a key or value in the + * specified map prevents it from being stored in this map. + * @throws NullPointerException the specified map is {@code null}, or if + * this map does not permit {@code null} keys or values, and the + * specified map contains {@code null} keys or values. + */ + public void putAll(final Map map) { + if (this.mutator == null) { + throw new UnsupportedOperationException("No mutator specified"); + } + this.mutator.putAll(this, this.composite, map); + } + + /** + * Removes the mapping for this key from this map if it is present + * (optional operation). More formally, if this map contains a mapping + * from key {@code k} to value {@code v} such that + * (key==null ? k==null : key.equals(k)), that mapping + * is removed. (The map can contain at most one such mapping.) + * + *

              Returns the value to which the map previously associated the key, or + * {@code null} if the map contained no mapping for this key. (A + * {@code null} return can also indicate that the map previously + * associated {@code null} with the specified key if the implementation + * supports {@code null} values.) The map will not contain a mapping for + * the specified key once the call returns. + * + * @param key key whose mapping is to be removed from the map. + * @return previous value associated with specified key, or {@code null} + * if there was no mapping for key. + * + * @throws ClassCastException if the key is of an inappropriate type for + * the composited map (optional). + * @throws NullPointerException if the key is {@code null} and the composited map + * does not not permit {@code null} keys (optional). + * @throws UnsupportedOperationException if the {@code remove} method is + * not supported by the composited map containing the key + */ + public V remove(final Object key) { + for (int i = this.composite.length - 1; i >= 0; --i) { + if (this.composite[i].containsKey(key)) { + return this.composite[i].remove(key); + } + } + return null; + } + + /** + * Returns the number of key-value mappings in this map. If the + * map contains more than {@code Integer.MAX_VALUE} elements, returns + * {@code Integer.MAX_VALUE}. + * + * @return the number of key-value mappings in this map. + */ + public int size() { + int size = 0; + for (int i = this.composite.length - 1; i >= 0; --i) { + size += this.composite[i].size(); + } + return size; + } + + /** + * Returns a collection view of the values contained in this map. The + * collection is backed by the map, so changes to the map are reflected in + * the collection, and vice-versa. If the map is modified while an + * iteration over the collection is in progress, the results of the + * iteration are undefined. The collection supports element removal, + * which removes the corresponding mapping from the map, via the + * {@code Iterator.remove}, {@code Collection.remove}, + * {@code removeAll}, {@code retainAll} and {@code clear} operations. + * It does not support the add or {@code addAll} operations. + * + * @return a collection view of the values contained in this map. + */ + public Collection values() { + final CompositeCollection values = new CompositeCollection(); + for (int i = composite.length - 1; i >= 0; --i) { + values.addComposited(composite[i].values()); + } + return values; + } + + /** + * Checks if this Map equals another as per the Map specification. + * + * @param obj the object to compare to + * @return true if the maps are equal + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof Map) { + final Map map = (Map) obj; + return this.entrySet().equals(map.entrySet()); + } + return false; + } + + /** + * Gets a hash code for the Map as per the Map specification. + * {@inheritDoc} + */ + @Override + public int hashCode() { + int code = 0; + for (final Map.Entry entry : entrySet()) { + code += entry.hashCode(); + } + return code; + } + + /** + * This interface allows definition for all of the indeterminate + * mutators in a CompositeMap, as well as providing a hook for + * callbacks on key collisions. + */ + public static interface MapMutator extends Serializable { + /** + * Called when adding a new Composited Map results in a + * key collision. + * + * @param composite the CompositeMap with the collision + * @param existing the Map already in the composite which contains the + * offending key + * @param added the Map being added + * @param intersect the intersection of the keysets of the existing and added maps + */ + void resolveCollision(CompositeMap composite, Map existing, + Map added, Collection intersect); + + /** + * Called when the CompositeMap.put() method is invoked. + * + * @param map the CompositeMap which is being modified + * @param composited array of Maps in the CompositeMap being modified + * @param key key with which the specified value is to be associated. + * @param value value to be associated with the specified key. + * @return previous value associated with specified key, or {@code null} + * if there was no mapping for key. A {@code null} return can + * also indicate that the map previously associated {@code null} + * with the specified key, if the implementation supports + * {@code null} values. + * + * @throws UnsupportedOperationException if not defined + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map. + * @throws IllegalArgumentException if some aspect of this key or value + * prevents it from being stored in this map. + * @throws NullPointerException this map does not permit {@code null} + * keys or values, and the specified key or value is + * {@code null}. + */ + V put(CompositeMap map, Map[] composited, K key, V value); + + /** + * Called when the CompositeMap.putAll() method is invoked. + * + * @param map the CompositeMap which is being modified + * @param composited array of Maps in the CompositeMap being modified + * @param mapToAdd Mappings to be stored in this CompositeMap + * + * @throws UnsupportedOperationException if not defined + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map. + * @throws IllegalArgumentException if some aspect of this key or value + * prevents it from being stored in this map. + * @throws NullPointerException this map does not permit {@code null} + * keys or values, and the specified key or value is + * {@code null}. + */ + void putAll(CompositeMap map, Map[] composited, + Map mapToAdd); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/DefaultedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/DefaultedMap.java new file mode 100644 index 000000000..7318b7317 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/DefaultedMap.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.collections4.Factory; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.functors.ConstantTransformer; +import org.apache.commons.collections4.functors.FactoryTransformer; + +/** + * Decorates another Map returning a default value if the map + * does not contain the requested key. + *

              + * When the {@link #get(Object)} method is called with a key that does not + * exist in the map, this map will return the default value specified in + * the constructor/factory. Only the get method is altered, so the + * {@link Map#containsKey(Object)} can be used to determine if a key really + * is in the map or not. + *

              + * The defaulted value is not added to the map. + * Compare this behaviour with {@link LazyMap}, which does add the value + * to the map (via a Transformer). + *

              + * For instance: + *

              + * Map map = new DefaultedMap("NULL");
              + * Object obj = map.get("Surname");
              + * // obj == "NULL"
              + * 
              + * After the above code is executed the map is still empty. + *

              + * Note that DefaultedMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + * + * @since 3.2 + * @version $Id: DefaultedMap.java 1686855 2015-06-22 13:00:27Z tn $ + * + * @see LazyMap + */ +public class DefaultedMap extends AbstractMapDecorator implements Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 19698628745827L; + + /** The transformer to use if the map does not contain a key */ + private final Transformer value; + + //----------------------------------------------------------------------- + /** + * Factory method to create a defaulting map. + *

              + * The value specified is returned when a missing key is found. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param defaultValue the default value to return when the key is not found + * @return a new defaulting map + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static DefaultedMap defaultedMap(final Map map, final V defaultValue) { + return new DefaultedMap(map, ConstantTransformer.constantTransformer(defaultValue)); + } + + /** + * Factory method to create a defaulting map. + *

              + * The factory specified is called when a missing key is found. + * The result will be returned as the result of the map get(key) method. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param factory the factory to use to create entries, must not be null + * @return a new defaulting map + * @throws NullPointerException if map or factory is null + * @since 4.0 + */ + public static DefaultedMap defaultedMap(final Map map, final Factory factory) { + if (factory == null) { + throw new IllegalArgumentException("Factory must not be null"); + } + return new DefaultedMap(map, FactoryTransformer.factoryTransformer(factory)); + } + + /** + * Factory method to create a defaulting map. + *

              + * The transformer specified is called when a missing key is found. + * The key is passed to the transformer as the input, and the result + * will be returned as the result of the map get(key) method. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param transformer the transformer to use as a factory to create entries, must not be null + * @return a new defaulting map + * @throws NullPointerException if map or factory is null + * @since 4.0 + */ + public static Map defaultedMap(final Map map, + final Transformer transformer) { + if (transformer == null) { + throw new IllegalArgumentException("Transformer must not be null"); + } + return new DefaultedMap(map, transformer); + } + + //----------------------------------------------------------------------- + /** + * Constructs a new empty DefaultedMap that decorates + * a HashMap. + *

              + * The object passed in will be returned by the map whenever an + * unknown key is requested. + * + * @param defaultValue the default value to return when the key is not found + */ + public DefaultedMap(final V defaultValue) { + this(ConstantTransformer.constantTransformer(defaultValue)); + } + + /** + * Constructs a new empty DefaultedMap that decorates a HashMap. + * + * @param defaultValueTransformer transformer to use to generate missing values. + */ + public DefaultedMap(final Transformer defaultValueTransformer) { + this(new HashMap(), defaultValueTransformer); + } + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @param defaultValueTransformer the value transformer to use + * @throws NullPointerException if map or transformer is null + */ + protected DefaultedMap(final Map map, final Transformer defaultValueTransformer) { + super(map); + if (defaultValueTransformer == null) { + throw new NullPointerException("Transformer must not be null."); + } + this.value = defaultValueTransformer; + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); + } + + //----------------------------------------------------------------------- + @Override + @SuppressWarnings("unchecked") + public V get(final Object key) { + // create value for key if key is not currently in the map + if (map.containsKey(key) == false) { + return value.transform((K) key); + } + return map.get(key); + } + + // no need to wrap keySet, entrySet or values as they are views of + // existing map entries - you can't do a map-style get on them. +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/EntrySetToMapIteratorAdapter.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/EntrySetToMapIteratorAdapter.java new file mode 100644 index 000000000..8623d1dfa --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/EntrySetToMapIteratorAdapter.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.ResettableIterator; + +/** + * Adapts a Map entrySet to the MapIterator interface. + * + * @since 4.0 + * @version $Id: EntrySetToMapIteratorAdapter.java 1494296 2013-06-18 20:54:29Z tn $ + */ +public class EntrySetToMapIteratorAdapter implements MapIterator, ResettableIterator { + + /** The adapted Map entry Set. */ + Set> entrySet; + + /** The resettable iterator in use. */ + transient Iterator> iterator; + + /** The currently positioned Map entry. */ + transient Map.Entry entry; + + /** + * Create a new EntrySetToMapIteratorAdapter. + * @param entrySet the entrySet to adapt + */ + public EntrySetToMapIteratorAdapter(final Set> entrySet) { + this.entrySet = entrySet; + reset(); + } + + /** + * {@inheritDoc} + */ + public K getKey() { + return current().getKey(); + } + + /** + * {@inheritDoc} + */ + public V getValue() { + return current().getValue(); + } + + /** + * {@inheritDoc} + */ + public V setValue(final V value) { + return current().setValue(value); + } + + /** + * {@inheritDoc} + */ + public boolean hasNext() { + return iterator.hasNext(); + } + + /** + * {@inheritDoc} + */ + public K next() { + entry = iterator.next(); + return getKey(); + } + + /** + * {@inheritDoc} + */ + public synchronized void reset() { + iterator = entrySet.iterator(); + } + + /** + * {@inheritDoc} + */ + public void remove() { + iterator.remove(); + entry = null; + } + + /** + * Get the currently active entry. + * @return Map.Entry + */ + protected synchronized Map.Entry current() { + if (entry == null) { + throw new IllegalStateException(); + } + return entry; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/FixedSizeMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/FixedSizeMap.java new file mode 100644 index 000000000..aff54aef7 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/FixedSizeMap.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.BoundedMap; +import org.apache.commons.collections4.collection.UnmodifiableCollection; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another Map to fix the size, preventing add/remove. + *

              + * Any action that would change the size of the map is disallowed. + * The put method is allowed to change the value associated with an existing + * key however. + *

              + * If trying to remove or clear the map, an UnsupportedOperationException is + * thrown. If trying to put a new mapping into the map, an + * IllegalArgumentException is thrown. This is because the put method can + * succeed if the mapping's key already exists in the map, so the put method + * is not always unsupported. + *

              + * Note that FixedSizeMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: FixedSizeMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class FixedSizeMap + extends AbstractMapDecorator + implements BoundedMap, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 7450927208116179316L; + + /** + * Factory method to create a fixed size map. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @return a new fixed size map + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static FixedSizeMap fixedSizeMap(final Map map) { + return new FixedSizeMap(map); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + protected FixedSizeMap(final Map map) { + super(map); + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + * @since 3.1 + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since 3.1 + */ + @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); // (1) + } + + //----------------------------------------------------------------------- + @Override + public V put(final K key, final V value) { + if (map.containsKey(key) == false) { + throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size"); + } + return map.put(key, value); + } + + @Override + public void putAll(final Map mapToCopy) { + for (final K key : mapToCopy.keySet()) { + if (!containsKey(key)) { + throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size"); + } + } + map.putAll(mapToCopy); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Map is fixed size"); + } + + @Override + public V remove(final Object key) { + throw new UnsupportedOperationException("Map is fixed size"); + } + + @Override + public Set> entrySet() { + final Set> set = map.entrySet(); + // unmodifiable set will still allow modification via Map.Entry objects + return UnmodifiableSet.unmodifiableSet(set); + } + + @Override + public Set keySet() { + final Set set = map.keySet(); + return UnmodifiableSet.unmodifiableSet(set); + } + + @Override + public Collection values() { + final Collection coll = map.values(); + return UnmodifiableCollection.unmodifiableCollection(coll); + } + + public boolean isFull() { + return true; + } + + public int maxSize() { + return size(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/FixedSizeSortedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/FixedSizeSortedMap.java new file mode 100644 index 000000000..efdf104b7 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/FixedSizeSortedMap.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; + +import org.apache.commons.collections4.BoundedMap; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.collection.UnmodifiableCollection; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another SortedMap to fix the size blocking add/remove. + *

              + * Any action that would change the size of the map is disallowed. + * The put method is allowed to change the value associated with an existing + * key however. + *

              + * If trying to remove or clear the map, an UnsupportedOperationException is + * thrown. If trying to put a new mapping into the map, an + * IllegalArgumentException is thrown. This is because the put method can + * succeed if the mapping's key already exists in the map, so the put method + * is not always unsupported. + *

              + * Note that FixedSizeSortedMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedSortedMap}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: FixedSizeSortedMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class FixedSizeSortedMap + extends AbstractSortedMapDecorator + implements BoundedMap, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 3126019624511683653L; + + /** + * Factory method to create a fixed size sorted map. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @return a new fixed size sorted map + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static FixedSizeSortedMap fixedSizeSortedMap(final SortedMap map) { + return new FixedSizeSortedMap(map); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + protected FixedSizeSortedMap(final SortedMap map) { + super(map); + } + + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + protected SortedMap getSortedMap() { + return (SortedMap) map; + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + */ + @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); // (1) + } + + //----------------------------------------------------------------------- + @Override + public V put(final K key, final V value) { + if (map.containsKey(key) == false) { + throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size"); + } + return map.put(key, value); + } + + @Override + public void putAll(final Map mapToCopy) { + if (CollectionUtils.isSubCollection(mapToCopy.keySet(), keySet())) { + throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size"); + } + map.putAll(mapToCopy); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Map is fixed size"); + } + + @Override + public V remove(final Object key) { + throw new UnsupportedOperationException("Map is fixed size"); + } + + @Override + public Set> entrySet() { + return UnmodifiableSet.unmodifiableSet(map.entrySet()); + } + + @Override + public Set keySet() { + return UnmodifiableSet.unmodifiableSet(map.keySet()); + } + + @Override + public Collection values() { + return UnmodifiableCollection.unmodifiableCollection(map.values()); + } + + //----------------------------------------------------------------------- + @Override + public SortedMap subMap(final K fromKey, final K toKey) { + return new FixedSizeSortedMap(getSortedMap().subMap(fromKey, toKey)); + } + + @Override + public SortedMap headMap(final K toKey) { + return new FixedSizeSortedMap(getSortedMap().headMap(toKey)); + } + + @Override + public SortedMap tailMap(final K fromKey) { + return new FixedSizeSortedMap(getSortedMap().tailMap(fromKey)); + } + + public boolean isFull() { + return true; + } + + public int maxSize() { + return size(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/Flat3Map.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/Flat3Map.java new file mode 100644 index 000000000..b6fa63e8f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/Flat3Map.java @@ -0,0 +1,1249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.commons.collections4.IterableMap; +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.ResettableIterator; +import org.apache.commons.collections4.iterators.EmptyIterator; +import org.apache.commons.collections4.iterators.EmptyMapIterator; + +/** + * A Map implementation that stores data in simple fields until + * the size is greater than 3. + *

              + * This map is designed for performance and can outstrip HashMap. + * It also has good garbage collection characteristics. + *

                + *
              • Optimised for operation at size 3 or less. + *
              • Still works well once size 3 exceeded. + *
              • Gets at size 3 or less are about 0-10% faster than HashMap, + *
              • Puts at size 3 or less are over 4 times faster than HashMap. + *
              • Performance 5% slower than HashMap once size 3 exceeded once. + *
              + * The design uses two distinct modes of operation - flat and delegate. + * While the map is size 3 or less, operations map straight onto fields using + * switch statements. Once size 4 is reached, the map switches to delegate mode + * and only switches back when cleared. In delegate mode, all operations are + * forwarded straight to a HashMap resulting in the 5% performance loss. + *

              + * The performance gains on puts are due to not needing to create a Map Entry + * object. This is a large saving not only in performance but in garbage collection. + *

              + * Whilst in flat mode this map is also easy for the garbage collector to dispatch. + * This is because it contains no complex objects or arrays which slow the progress. + *

              + * Do not use Flat3Map if the size is likely to grow beyond 3. + *

              + * Note that Flat3Map is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + * + * @since 3.0 + * @version $Id: Flat3Map.java 1477799 2013-04-30 19:56:11Z tn $ + */ +public class Flat3Map implements IterableMap, Serializable, Cloneable { + + /** Serialization version */ + private static final long serialVersionUID = -6701087419741928296L; + + /** The size of the map, used while in flat mode */ + private transient int size; + /** Hash, used while in flat mode */ + private transient int hash1; + /** Hash, used while in flat mode */ + private transient int hash2; + /** Hash, used while in flat mode */ + private transient int hash3; + /** Key, used while in flat mode */ + private transient K key1; + /** Key, used while in flat mode */ + private transient K key2; + /** Key, used while in flat mode */ + private transient K key3; + /** Value, used while in flat mode */ + private transient V value1; + /** Value, used while in flat mode */ + private transient V value2; + /** Value, used while in flat mode */ + private transient V value3; + /** Map, used while in delegate mode */ + private transient AbstractHashedMap delegateMap; + + /** + * Constructor. + */ + public Flat3Map() { + super(); + } + + /** + * Constructor copying elements from another map. + * + * @param map the map to copy + * @throws NullPointerException if the map is null + */ + public Flat3Map(final Map map) { + super(); + putAll(map); + } + + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the key specified. + * + * @param key the key + * @return the mapped value, null if no match + */ + public V get(final Object key) { + if (delegateMap != null) { + return delegateMap.get(key); + } + if (key == null) { + switch (size) { + // drop through + case 3: + if (key3 == null) { + return value3; + } + case 2: + if (key2 == null) { + return value2; + } + case 1: + if (key1 == null) { + return value1; + } + } + } else { + if (size > 0) { + final int hashCode = key.hashCode(); + switch (size) { + // drop through + case 3: + if (hash3 == hashCode && key.equals(key3)) { + return value3; + } + case 2: + if (hash2 == hashCode && key.equals(key2)) { + return value2; + } + case 1: + if (hash1 == hashCode && key.equals(key1)) { + return value1; + } + } + } + } + return null; + } + + /** + * Gets the size of the map. + * + * @return the size + */ + public int size() { + if (delegateMap != null) { + return delegateMap.size(); + } + return size; + } + + /** + * Checks whether the map is currently empty. + * + * @return true if the map is currently size zero + */ + public boolean isEmpty() { + return size() == 0; + } + + //----------------------------------------------------------------------- + /** + * Checks whether the map contains the specified key. + * + * @param key the key to search for + * @return true if the map contains the key + */ + public boolean containsKey(final Object key) { + if (delegateMap != null) { + return delegateMap.containsKey(key); + } + if (key == null) { + switch (size) { // drop through + case 3: + if (key3 == null) { + return true; + } + case 2: + if (key2 == null) { + return true; + } + case 1: + if (key1 == null) { + return true; + } + } + } else { + if (size > 0) { + final int hashCode = key.hashCode(); + switch (size) { // drop through + case 3: + if (hash3 == hashCode && key.equals(key3)) { + return true; + } + case 2: + if (hash2 == hashCode && key.equals(key2)) { + return true; + } + case 1: + if (hash1 == hashCode && key.equals(key1)) { + return true; + } + } + } + } + return false; + } + + /** + * Checks whether the map contains the specified value. + * + * @param value the value to search for + * @return true if the map contains the key + */ + public boolean containsValue(final Object value) { + if (delegateMap != null) { + return delegateMap.containsValue(value); + } + if (value == null) { // drop through + switch (size) { + case 3: + if (value3 == null) { + return true; + } + case 2: + if (value2 == null) { + return true; + } + case 1: + if (value1 == null) { + return true; + } + } + } else { + switch (size) { // drop through + case 3: + if (value.equals(value3)) { + return true; + } + case 2: + if (value.equals(value2)) { + return true; + } + case 1: + if (value.equals(value1)) { + return true; + } + } + } + return false; + } + + //----------------------------------------------------------------------- + /** + * Puts a key-value mapping into this map. + * + * @param key the key to add + * @param value the value to add + * @return the value previously mapped to this key, null if none + */ + public V put(final K key, final V value) { + if (delegateMap != null) { + return delegateMap.put(key, value); + } + // change existing mapping + if (key == null) { + switch (size) { // drop through + case 3: + if (key3 == null) { + final V old = value3; + value3 = value; + return old; + } + case 2: + if (key2 == null) { + final V old = value2; + value2 = value; + return old; + } + case 1: + if (key1 == null) { + final V old = value1; + value1 = value; + return old; + } + } + } else { + if (size > 0) { + final int hashCode = key.hashCode(); + switch (size) { // drop through + case 3: + if (hash3 == hashCode && key.equals(key3)) { + final V old = value3; + value3 = value; + return old; + } + case 2: + if (hash2 == hashCode && key.equals(key2)) { + final V old = value2; + value2 = value; + return old; + } + case 1: + if (hash1 == hashCode && key.equals(key1)) { + final V old = value1; + value1 = value; + return old; + } + } + } + } + + // add new mapping + switch (size) { + default: + convertToMap(); + delegateMap.put(key, value); + return null; + case 2: + hash3 = key == null ? 0 : key.hashCode(); + key3 = key; + value3 = value; + break; + case 1: + hash2 = key == null ? 0 : key.hashCode(); + key2 = key; + value2 = value; + break; + case 0: + hash1 = key == null ? 0 : key.hashCode(); + key1 = key; + value1 = value; + break; + } + size++; + return null; + } + + /** + * Puts all the values from the specified map into this map. + * + * @param map the map to add + * @throws NullPointerException if the map is null + */ + public void putAll(final Map map) { + final int size = map.size(); + if (size == 0) { + return; + } + if (delegateMap != null) { + delegateMap.putAll(map); + return; + } + if (size < 4) { + for (final Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } else { + convertToMap(); + delegateMap.putAll(map); + } + } + + /** + * Converts the flat map data to a map. + */ + private void convertToMap() { + delegateMap = createDelegateMap(); + switch (size) { // drop through + case 3: + delegateMap.put(key3, value3); + case 2: + delegateMap.put(key2, value2); + case 1: + delegateMap.put(key1, value1); + case 0: + break; + default: + throw new IllegalStateException("Invalid map index: " + size); + } + + size = 0; + hash1 = hash2 = hash3 = 0; + key1 = key2 = key3 = null; + value1 = value2 = value3 = null; + } + + /** + * Create an instance of the map used for storage when in delegation mode. + *

              + * This can be overridden by subclasses to provide a different map implementation. + * Not every AbstractHashedMap is suitable, identity and reference based maps + * would be poor choices. + * + * @return a new AbstractHashedMap or subclass + * @since 3.1 + */ + protected AbstractHashedMap createDelegateMap() { + return new HashedMap(); + } + + /** + * Removes the specified mapping from this map. + * + * @param key the mapping to remove + * @return the value mapped to the removed key, null if key not in map + */ + public V remove(final Object key) { + if (delegateMap != null) { + return delegateMap.remove(key); + } + if (size == 0) { + return null; + } + if (key == null) { + switch (size) { // drop through + case 3: + if (key3 == null) { + final V old = value3; + hash3 = 0; + key3 = null; + value3 = null; + size = 2; + return old; + } + if (key2 == null) { + final V old = value2; + hash2 = hash3; + key2 = key3; + value2 = value3; + hash3 = 0; + key3 = null; + value3 = null; + size = 2; + return old; + } + if (key1 == null) { + final V old = value1; + hash1 = hash3; + key1 = key3; + value1 = value3; + hash3 = 0; + key3 = null; + value3 = null; + size = 2; + return old; + } + return null; + case 2: + if (key2 == null) { + final V old = value2; + hash2 = 0; + key2 = null; + value2 = null; + size = 1; + return old; + } + if (key1 == null) { + final V old = value1; + hash1 = hash2; + key1 = key2; + value1 = value2; + hash2 = 0; + key2 = null; + value2 = null; + size = 1; + return old; + } + return null; + case 1: + if (key1 == null) { + final V old = value1; + hash1 = 0; + key1 = null; + value1 = null; + size = 0; + return old; + } + } + } else { + if (size > 0) { + final int hashCode = key.hashCode(); + switch (size) { // drop through + case 3: + if (hash3 == hashCode && key.equals(key3)) { + final V old = value3; + hash3 = 0; + key3 = null; + value3 = null; + size = 2; + return old; + } + if (hash2 == hashCode && key.equals(key2)) { + final V old = value2; + hash2 = hash3; + key2 = key3; + value2 = value3; + hash3 = 0; + key3 = null; + value3 = null; + size = 2; + return old; + } + if (hash1 == hashCode && key.equals(key1)) { + final V old = value1; + hash1 = hash3; + key1 = key3; + value1 = value3; + hash3 = 0; + key3 = null; + value3 = null; + size = 2; + return old; + } + return null; + case 2: + if (hash2 == hashCode && key.equals(key2)) { + final V old = value2; + hash2 = 0; + key2 = null; + value2 = null; + size = 1; + return old; + } + if (hash1 == hashCode && key.equals(key1)) { + final V old = value1; + hash1 = hash2; + key1 = key2; + value1 = value2; + hash2 = 0; + key2 = null; + value2 = null; + size = 1; + return old; + } + return null; + case 1: + if (hash1 == hashCode && key.equals(key1)) { + final V old = value1; + hash1 = 0; + key1 = null; + value1 = null; + size = 0; + return old; + } + } + } + } + return null; + } + + /** + * Clears the map, resetting the size to zero and nullifying references + * to avoid garbage collection issues. + */ + public void clear() { + if (delegateMap != null) { + delegateMap.clear(); // should aid gc + delegateMap = null; // switch back to flat mode + } else { + size = 0; + hash1 = hash2 = hash3 = 0; + key1 = key2 = key3 = null; + value1 = value2 = value3 = null; + } + } + + //----------------------------------------------------------------------- + /** + * Gets an iterator over the map. + * Changes made to the iterator affect this map. + *

              + * A MapIterator returns the keys in the map. It also provides convenient + * methods to get the key and value, and set the value. + * It avoids the need to create an entrySet/keySet/values object. + * It also avoids creating the Map Entry object. + * + * @return the map iterator + */ + public MapIterator mapIterator() { + if (delegateMap != null) { + return delegateMap.mapIterator(); + } + if (size == 0) { + return EmptyMapIterator.emptyMapIterator(); + } + return new FlatMapIterator(this); + } + + /** + * FlatMapIterator + */ + static class FlatMapIterator implements MapIterator, ResettableIterator { + private final Flat3Map parent; + private int nextIndex = 0; + private boolean canRemove = false; + + FlatMapIterator(final Flat3Map parent) { + super(); + this.parent = parent; + } + + public boolean hasNext() { + return nextIndex < parent.size; + } + + public K next() { + if (hasNext() == false) { + throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); + } + canRemove = true; + nextIndex++; + return getKey(); + } + + public void remove() { + if (canRemove == false) { + throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); + } + parent.remove(getKey()); + nextIndex--; + canRemove = false; + } + + public K getKey() { + if (canRemove == false) { + throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); + } + switch (nextIndex) { + case 3: + return parent.key3; + case 2: + return parent.key2; + case 1: + return parent.key1; + } + throw new IllegalStateException("Invalid map index: " + nextIndex); + } + + public V getValue() { + if (canRemove == false) { + throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); + } + switch (nextIndex) { + case 3: + return parent.value3; + case 2: + return parent.value2; + case 1: + return parent.value1; + } + throw new IllegalStateException("Invalid map index: " + nextIndex); + } + + public V setValue(final V value) { + if (canRemove == false) { + throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); + } + final V old = getValue(); + switch (nextIndex) { + case 3: + parent.value3 = value; + break; + case 2: + parent.value2 = value; + break; + case 1: + parent.value1 = value; + break; + default: + throw new IllegalStateException("Invalid map index: " + nextIndex); + } + return old; + } + + public void reset() { + nextIndex = 0; + canRemove = false; + } + + @Override + public String toString() { + if (canRemove) { + return "Iterator[" + getKey() + "=" + getValue() + "]"; + } + return "Iterator[]"; + } + } + + /** + * Gets the entrySet view of the map. + * Changes made to the view affect this map. + *

              + * NOTE: from 4.0, the returned Map Entry will be an independent object and will + * not change anymore as the iterator progresses. To avoid this additional object + * creation and simply iterate through the entries, use {@link #mapIterator()}. + * + * @return the entrySet view + */ + public Set> entrySet() { + if (delegateMap != null) { + return delegateMap.entrySet(); + } + return new EntrySet(this); + } + + /** + * EntrySet + */ + static class EntrySet extends AbstractSet> { + private final Flat3Map parent; + + EntrySet(final Flat3Map parent) { + super(); + this.parent = parent; + } + + @Override + public int size() { + return parent.size(); + } + + @Override + public void clear() { + parent.clear(); + } + + @Override + public boolean remove(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry entry = (Map.Entry) obj; + final Object key = entry.getKey(); + final boolean result = parent.containsKey(key); + parent.remove(key); + return result; + } + + @Override + public Iterator> iterator() { + if (parent.delegateMap != null) { + return parent.delegateMap.entrySet().iterator(); + } + if (parent.size() == 0) { + return EmptyIterator.>emptyIterator(); + } + return new EntrySetIterator(parent); + } + } + + static class FlatMapEntry implements Map.Entry { + private final Flat3Map parent; + private final int index; + private volatile boolean removed; + + public FlatMapEntry(final Flat3Map parent, final int index) { + this.parent = parent; + this.index = index; + this.removed = false; + } + + /** + * Used by the iterator that created this entry to indicate that + * {@link java.util.Iterator#remove()} has been called. + *

              + * As a consequence, all subsequent call to {@link #getKey()}, + * {@link #setValue(Object)} and {@link #getValue()} will fail. + * + * @param flag + */ + void setRemoved(final boolean flag) { + this.removed = flag; + } + + public K getKey() { + if (removed) { + throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); + } + switch (index) { + case 3: + return parent.key3; + case 2: + return parent.key2; + case 1: + return parent.key1; + } + throw new IllegalStateException("Invalid map index: " + index); + } + + public V getValue() { + if (removed) { + throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); + } + switch (index) { + case 3: + return parent.value3; + case 2: + return parent.value2; + case 1: + return parent.value1; + } + throw new IllegalStateException("Invalid map index: " + index); + } + + public V setValue(final V value) { + if (removed) { + throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); + } + final V old = getValue(); + switch (index) { + case 3: + parent.value3 = value; + break; + case 2: + parent.value2 = value; + break; + case 1: + parent.value1 = value; + break; + default: + throw new IllegalStateException("Invalid map index: " + index); + } + return old; + } + + @Override + public boolean equals(final Object obj) { + if (removed) { + return false; + } + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry other = (Map.Entry) obj; + final Object key = getKey(); + final Object value = getValue(); + return (key == null ? other.getKey() == null : key.equals(other.getKey())) && + (value == null ? other.getValue() == null : value.equals(other.getValue())); + } + + @Override + public int hashCode() { + if (removed) { + return 0; + } + final Object key = getKey(); + final Object value = getValue(); + return (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()); + } + + @Override + public String toString() { + if (!removed) { + return getKey() + "=" + getValue(); + } + return ""; + } + + } + + static abstract class EntryIterator { + private final Flat3Map parent; + private int nextIndex = 0; + private FlatMapEntry currentEntry = null; + + /** + * Create a new Flat3Map.EntryIterator. + */ + public EntryIterator(final Flat3Map parent) { + this.parent = parent; + } + + public boolean hasNext() { + return nextIndex < parent.size; + } + + public Map.Entry nextEntry() { + if (!hasNext()) { + throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); + } + currentEntry = new FlatMapEntry(parent, ++nextIndex); + return currentEntry; + } + + public void remove() { + if (currentEntry == null) { + throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); + } + currentEntry.setRemoved(true); + parent.remove(currentEntry.getKey()); + nextIndex--; + currentEntry = null; + } + + } + + /** + * EntrySetIterator and MapEntry + */ + static class EntrySetIterator extends EntryIterator implements Iterator> { + EntrySetIterator(final Flat3Map parent) { + super(parent); + } + + public Map.Entry next() { + return nextEntry(); + } + } + + /** + * Gets the keySet view of the map. + * Changes made to the view affect this map. + * To simply iterate through the keys, use {@link #mapIterator()}. + * + * @return the keySet view + */ + public Set keySet() { + if (delegateMap != null) { + return delegateMap.keySet(); + } + return new KeySet(this); + } + + /** + * KeySet + */ + static class KeySet extends AbstractSet { + private final Flat3Map parent; + + KeySet(final Flat3Map parent) { + super(); + this.parent = parent; + } + + @Override + public int size() { + return parent.size(); + } + + @Override + public void clear() { + parent.clear(); + } + + @Override + public boolean contains(final Object key) { + return parent.containsKey(key); + } + + @Override + public boolean remove(final Object key) { + final boolean result = parent.containsKey(key); + parent.remove(key); + return result; + } + + @Override + public Iterator iterator() { + if (parent.delegateMap != null) { + return parent.delegateMap.keySet().iterator(); + } + if (parent.size() == 0) { + return EmptyIterator.emptyIterator(); + } + return new KeySetIterator(parent); + } + } + + /** + * KeySetIterator + */ + static class KeySetIterator extends EntryIterator implements Iterator{ + + @SuppressWarnings("unchecked") + KeySetIterator(final Flat3Map parent) { + super((Flat3Map) parent); + } + + public K next() { + return nextEntry().getKey(); + } + } + + /** + * Gets the values view of the map. + * Changes made to the view affect this map. + * To simply iterate through the values, use {@link #mapIterator()}. + * + * @return the values view + */ + public Collection values() { + if (delegateMap != null) { + return delegateMap.values(); + } + return new Values(this); + } + + /** + * Values + */ + static class Values extends AbstractCollection { + private final Flat3Map parent; + + Values(final Flat3Map parent) { + super(); + this.parent = parent; + } + + @Override + public int size() { + return parent.size(); + } + + @Override + public void clear() { + parent.clear(); + } + + @Override + public boolean contains(final Object value) { + return parent.containsValue(value); + } + + @Override + public Iterator iterator() { + if (parent.delegateMap != null) { + return parent.delegateMap.values().iterator(); + } + if (parent.size() == 0) { + return EmptyIterator.emptyIterator(); + } + return new ValuesIterator(parent); + } + } + + /** + * ValuesIterator + */ + static class ValuesIterator extends EntryIterator implements Iterator { + + @SuppressWarnings("unchecked") + ValuesIterator(final Flat3Map parent) { + super((Flat3Map) parent); + } + + public V next() { + return nextEntry().getValue(); + } + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeInt(size()); + for (final MapIterator it = mapIterator(); it.hasNext();) { + out.writeObject(it.next()); // key + out.writeObject(it.getValue()); // value + } + } + + /** + * Read the map in using a custom routine. + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + final int count = in.readInt(); + if (count > 3) { + delegateMap = createDelegateMap(); + } + for (int i = count; i > 0; i--) { + put((K) in.readObject(), (V) in.readObject()); + } + } + + //----------------------------------------------------------------------- + /** + * Clones the map without cloning the keys or values. + * + * @return a shallow clone + * @since 3.1 + */ + @Override + @SuppressWarnings("unchecked") + public Flat3Map clone() { + try { + final Flat3Map cloned = (Flat3Map) super.clone(); + if (cloned.delegateMap != null) { + cloned.delegateMap = cloned.delegateMap.clone(); + } + return cloned; + } catch (final CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + /** + * Compares this map with another. + * + * @param obj the object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (delegateMap != null) { + return delegateMap.equals(obj); + } + if (obj instanceof Map == false) { + return false; + } + final Map other = (Map) obj; + if (size != other.size()) { + return false; + } + if (size > 0) { + Object otherValue = null; + switch (size) { // drop through + case 3: + if (other.containsKey(key3) == false) { + return false; + } + otherValue = other.get(key3); + if (value3 == null ? otherValue != null : !value3.equals(otherValue)) { + return false; + } + case 2: + if (other.containsKey(key2) == false) { + return false; + } + otherValue = other.get(key2); + if (value2 == null ? otherValue != null : !value2.equals(otherValue)) { + return false; + } + case 1: + if (other.containsKey(key1) == false) { + return false; + } + otherValue = other.get(key1); + if (value1 == null ? otherValue != null : !value1.equals(otherValue)) { + return false; + } + } + } + return true; + } + + /** + * Gets the standard Map hashCode. + * + * @return the hash code defined in the Map interface + */ + @Override + public int hashCode() { + if (delegateMap != null) { + return delegateMap.hashCode(); + } + int total = 0; + switch (size) { // drop through + case 3: + total += hash3 ^ (value3 == null ? 0 : value3.hashCode()); + case 2: + total += hash2 ^ (value2 == null ? 0 : value2.hashCode()); + case 1: + total += hash1 ^ (value1 == null ? 0 : value1.hashCode()); + case 0: + break; + default: + throw new IllegalStateException("Invalid map index: " + size); + } + return total; + } + + /** + * Gets the map as a String. + * + * @return a string version of the map + */ + @Override + public String toString() { + if (delegateMap != null) { + return delegateMap.toString(); + } + if (size == 0) { + return "{}"; + } + final StringBuilder buf = new StringBuilder(128); + buf.append('{'); + switch (size) { // drop through + case 3: + buf.append(key3 == this ? "(this Map)" : key3); + buf.append('='); + buf.append(value3 == this ? "(this Map)" : value3); + buf.append(','); + case 2: + buf.append(key2 == this ? "(this Map)" : key2); + buf.append('='); + buf.append(value2 == this ? "(this Map)" : value2); + buf.append(','); + case 1: + buf.append(key1 == this ? "(this Map)" : key1); + buf.append('='); + buf.append(value1 == this ? "(this Map)" : value1); + break; + // case 0: has already been dealt with + default: + throw new IllegalStateException("Invalid map index: " + size); + } + buf.append('}'); + return buf.toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/HashedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/HashedMap.java new file mode 100644 index 000000000..3e42287d8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/HashedMap.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Map; + +/** + * A Map implementation that is a general purpose alternative + * to HashMap. + *

              + * This implementation improves on the JDK1.4 HashMap by adding the + * {@link org.apache.commons.collections4.MapIterator MapIterator} + * functionality and many methods for subclassing. + *

              + * Note that HashedMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + * + * @since 3.0 + * @version $Id: HashedMap.java 1533984 2013-10-20 21:12:51Z tn $ + */ +public class HashedMap + extends AbstractHashedMap implements Serializable, Cloneable { + + /** Serialisation version */ + private static final long serialVersionUID = -1788199231038721040L; + + /** + * Constructs a new empty map with default size and load factor. + */ + public HashedMap() { + super(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_THRESHOLD); + } + + /** + * Constructs a new, empty map with the specified initial capacity. + * + * @param initialCapacity the initial capacity + * @throws IllegalArgumentException if the initial capacity is negative + */ + public HashedMap(final int initialCapacity) { + super(initialCapacity); + } + + /** + * Constructs a new, empty map with the specified initial capacity and + * load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @throws IllegalArgumentException if the initial capacity is negative + * @throws IllegalArgumentException if the load factor is less than zero + */ + public HashedMap(final int initialCapacity, final float loadFactor) { + super(initialCapacity, loadFactor); + } + + /** + * Constructor copying elements from another map. + * + * @param map the map to copy + * @throws NullPointerException if the map is null + */ + public HashedMap(final Map map) { + super(map); + } + + //----------------------------------------------------------------------- + /** + * Clones the map without cloning the keys or values. + * + * @return a shallow clone + */ + @Override + public HashedMap clone() { + return (HashedMap) super.clone(); + } + + /** + * Write the map out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + + /** + * Read the map in using a custom routine. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + doReadObject(in); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/LRUMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/LRUMap.java new file mode 100644 index 000000000..6f384bb6f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/LRUMap.java @@ -0,0 +1,522 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Map; + +import org.apache.commons.collections4.BoundedMap; + +/** + * A Map implementation with a fixed maximum size which removes + * the least recently used entry if an entry is added when full. + *

              + * The least recently used algorithm works on the get and put operations only. + * Iteration of any kind, including setting the value by iteration, does not + * change the order. Queries such as containsKey and containsValue or access + * via views also do not change the order. + *

              + * A somewhat subtle ramification of the least recently used + * algorithm is that calls to {@link #get(Object)} stand a very good chance + * of modifying the map's iteration order and thus invalidating any + * iterators currently in use. It is therefore suggested that iterations + * over an {@link LRUMap} instance access entry values only through a + * {@link org.apache.commons.collections4.MapIterator MapIterator} or {@link #entrySet()} iterator. + *

              + * The map implements OrderedMap and entries may be queried using + * the bidirectional OrderedMapIterator. The order returned is + * least recently used to most recently used. Iterators from map views can + * also be cast to OrderedIterator if required. + *

              + * All the available iterators can be reset back to the start by casting to + * ResettableIterator and calling reset(). + *

              + * Note that LRUMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * NullPointerException's when accessed by concurrent threads. + * + * @since 3.0 (previously in main package v1.0) + * @version $Id: LRUMap.java 1683016 2015-06-01 22:40:56Z tn $ + */ +public class LRUMap + extends AbstractLinkedMap implements BoundedMap, Serializable, Cloneable { + + /** Serialisation version */ + private static final long serialVersionUID = -612114643488955218L; + /** Default maximum size */ + protected static final int DEFAULT_MAX_SIZE = 100; + + /** Maximum size */ + private transient int maxSize; + /** Scan behaviour */ + private boolean scanUntilRemovable; + + /** + * Constructs a new empty map with a maximum size of 100. + */ + public LRUMap() { + this(DEFAULT_MAX_SIZE, DEFAULT_LOAD_FACTOR, false); + } + + /** + * Constructs a new, empty map with the specified maximum size. + * + * @param maxSize the maximum size of the map + * @throws IllegalArgumentException if the maximum size is less than one + */ + public LRUMap(final int maxSize) { + this(maxSize, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs a new, empty map with the specified maximum size. + * + * @param maxSize the maximum size of the map + * @param initialSize the initial size of the map + * @throws IllegalArgumentException if the maximum size is less than one + * @throws IllegalArgumentException if the initial size is negative or larger than the maximum size + * @since 4.1 + */ + public LRUMap(final int maxSize, final int initialSize) { + this(maxSize, initialSize, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs a new, empty map with the specified maximum size. + * + * @param maxSize the maximum size of the map + * @param scanUntilRemovable scan until a removeable entry is found, default false + * @throws IllegalArgumentException if the maximum size is less than one + * @since 3.1 + */ + public LRUMap(final int maxSize, final boolean scanUntilRemovable) { + this(maxSize, DEFAULT_LOAD_FACTOR, scanUntilRemovable); + } + + /** + * Constructs a new, empty map with the specified max capacity and + * load factor. + * + * @param maxSize the maximum size of the map + * @param loadFactor the load factor + * @throws IllegalArgumentException if the maximum size is less than one + * @throws IllegalArgumentException if the load factor is less than zero + */ + public LRUMap(final int maxSize, final float loadFactor) { + this(maxSize, loadFactor, false); + } + + /** + * Constructs a new, empty map with the specified max / initial capacity and + * load factor. + * + * @param maxSize the maximum size of the map + * @param initialSize the initial size of the map + * @param loadFactor the load factor + * @throws IllegalArgumentException if the maximum size is less than one + * @throws IllegalArgumentException if the initial size is negative or larger than the maximum size + * @throws IllegalArgumentException if the load factor is less than zero + * @since 4.1 + */ + public LRUMap(final int maxSize, final int initialSize, final float loadFactor) { + this(maxSize, initialSize, loadFactor, false); + } + + /** + * Constructs a new, empty map with the specified max capacity and load factor. + * + * @param maxSize the maximum size of the map + * @param loadFactor the load factor + * @param scanUntilRemovable scan until a removeable entry is found, default false + * @throws IllegalArgumentException if the maximum size is less than one + * @throws IllegalArgumentException if the load factor is less than zero + * @since 3.1 + */ + public LRUMap(final int maxSize, final float loadFactor, final boolean scanUntilRemovable) { + this(maxSize, maxSize, loadFactor, scanUntilRemovable); + } + + /** + * Constructs a new, empty map with the specified max / initial capacity and load factor. + * + * @param maxSize the maximum size of the map + * @param initialSize the initial size of the map + * @param loadFactor the load factor + * @param scanUntilRemovable scan until a removeable entry is found, default false + * @throws IllegalArgumentException if the maximum size is less than one + * @throws IllegalArgumentException if the initial size is negative or larger than the maximum size + * @throws IllegalArgumentException if the load factor is less than zero + * @since 4.1 + */ + public LRUMap(final int maxSize, + final int initialSize, + final float loadFactor, + final boolean scanUntilRemovable) { + + super(initialSize, loadFactor); + if (maxSize < 1) { + throw new IllegalArgumentException("LRUMap max size must be greater than 0"); + } + if (initialSize > maxSize) { + throw new IllegalArgumentException("LRUMap initial size must not be greather than max size"); + } + this.maxSize = maxSize; + this.scanUntilRemovable = scanUntilRemovable; + } + + /** + * Constructor copying elements from another map. + *

              + * The maximum size is set from the map's size. + * + * @param map the map to copy + * @throws NullPointerException if the map is null + * @throws IllegalArgumentException if the map is empty + */ + public LRUMap(final Map map) { + this(map, false); + } + + /** + * Constructor copying elements from another map. + *

              + * The maximum size is set from the map's size. + * + * @param map the map to copy + * @param scanUntilRemovable scan until a removeable entry is found, default false + * @throws NullPointerException if the map is null + * @throws IllegalArgumentException if the map is empty + * @since 3.1 + */ + public LRUMap(final Map map, final boolean scanUntilRemovable) { + this(map.size(), DEFAULT_LOAD_FACTOR, scanUntilRemovable); + putAll(map); + } + + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the key specified. + *

              + * This operation changes the position of the key in the map to the + * most recently used position (last). + * + * @param key the key + * @return the mapped value, null if no match + */ + @Override + public V get(final Object key) { + return get(key, true); + } + + /** + * Gets the value mapped to the key specified. + *

              + * If {@code updateToMRU} is {@code true}, the position of the key in the map + * is changed to the most recently used position (last), otherwise the iteration + * order is not changed by this operation. + * + * @param key the key + * @param updateToMRU whether the key shall be updated to the + * most recently used position + * @return the mapped value, null if no match + * @since 4.1 + */ + public V get(final Object key, final boolean updateToMRU) { + final LinkEntry entry = getEntry(key); + if (entry == null) { + return null; + } + if (updateToMRU) { + moveToMRU(entry); + } + return entry.getValue(); + } + + //----------------------------------------------------------------------- + /** + * Moves an entry to the MRU position at the end of the list. + *

              + * This implementation moves the updated entry to the end of the list. + * + * @param entry the entry to update + */ + protected void moveToMRU(final LinkEntry entry) { + if (entry.after != header) { + modCount++; + // remove + if(entry.before == null) { + throw new IllegalStateException("Entry.before is null." + + " Please check that your keys are immutable, and that you have used synchronization properly." + + " If so, then please report this to dev@commons.apache.org as a bug."); + } + entry.before.after = entry.after; + entry.after.before = entry.before; + // add first + entry.after = header; + entry.before = header.before; + header.before.after = entry; + header.before = entry; + } else if (entry == header) { + throw new IllegalStateException("Can't move header to MRU" + + " (please report this to dev@commons.apache.org)"); + } + } + + /** + * Updates an existing key-value mapping. + *

              + * This implementation moves the updated entry to the end of the list + * using {@link #moveToMRU(AbstractLinkedMap.LinkEntry)}. + * + * @param entry the entry to update + * @param newValue the new value to store + */ + @Override + protected void updateEntry(final HashEntry entry, final V newValue) { + moveToMRU((LinkEntry) entry); // handles modCount + entry.setValue(newValue); + } + + /** + * Adds a new key-value mapping into this map. + *

              + * This implementation checks the LRU size and determines whether to + * discard an entry or not using {@link #removeLRU(AbstractLinkedMap.LinkEntry)}. + *

              + * From Commons Collections 3.1 this method uses {@link #isFull()} rather + * than accessing size and maxSize directly. + * It also handles the scanUntilRemovable functionality. + * + * @param hashIndex the index into the data array to store at + * @param hashCode the hash code of the key to add + * @param key the key to add + * @param value the value to add + */ + @Override + protected void addMapping(final int hashIndex, final int hashCode, final K key, final V value) { + if (isFull()) { + LinkEntry reuse = header.after; + boolean removeLRUEntry = false; + if (scanUntilRemovable) { + while (reuse != header && reuse != null) { + if (removeLRU(reuse)) { + removeLRUEntry = true; + break; + } + reuse = reuse.after; + } + if (reuse == null) { + throw new IllegalStateException( + "Entry.after=null, header.after" + header.after + " header.before" + header.before + + " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize + + " Please check that your keys are immutable, and that you have used synchronization properly." + + " If so, then please report this to dev@commons.apache.org as a bug."); + } + } else { + removeLRUEntry = removeLRU(reuse); + } + + if (removeLRUEntry) { + if (reuse == null) { + throw new IllegalStateException( + "reuse=null, header.after=" + header.after + " header.before" + header.before + + " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize + + " Please check that your keys are immutable, and that you have used synchronization properly." + + " If so, then please report this to dev@commons.apache.org as a bug."); + } + reuseMapping(reuse, hashIndex, hashCode, key, value); + } else { + super.addMapping(hashIndex, hashCode, key, value); + } + } else { + super.addMapping(hashIndex, hashCode, key, value); + } + } + + /** + * Reuses an entry by removing it and moving it to a new place in the map. + *

              + * This method uses {@link #removeEntry}, {@link #reuseEntry} and {@link #addEntry}. + * + * @param entry the entry to reuse + * @param hashIndex the index into the data array to store at + * @param hashCode the hash code of the key to add + * @param key the key to add + * @param value the value to add + */ + protected void reuseMapping(final LinkEntry entry, final int hashIndex, final int hashCode, + final K key, final V value) { + // find the entry before the entry specified in the hash table + // remember that the parameters (except the first) refer to the new entry, + // not the old one + try { + final int removeIndex = hashIndex(entry.hashCode, data.length); + final HashEntry[] tmp = data; // may protect against some sync issues + HashEntry loop = tmp[removeIndex]; + HashEntry previous = null; + while (loop != entry && loop != null) { + previous = loop; + loop = loop.next; + } + if (loop == null) { + throw new IllegalStateException( + "Entry.next=null, data[removeIndex]=" + data[removeIndex] + " previous=" + previous + + " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize + + " Please check that your keys are immutable, and that you have used synchronization properly." + + " If so, then please report this to dev@commons.apache.org as a bug."); + } + + // reuse the entry + modCount++; + removeEntry(entry, removeIndex, previous); + reuseEntry(entry, hashIndex, hashCode, key, value); + addEntry(entry, hashIndex); + } catch (final NullPointerException ex) { + throw new IllegalStateException( + "NPE, entry=" + entry + " entryIsHeader=" + (entry==header) + + " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize + + " Please check that your keys are immutable, and that you have used synchronization properly." + + " If so, then please report this to dev@commons.apache.org as a bug."); + } + } + + /** + * Subclass method to control removal of the least recently used entry from the map. + *

              + * This method exists for subclasses to override. A subclass may wish to + * provide cleanup of resources when an entry is removed. For example: + *

              +     * protected boolean removeLRU(LinkEntry entry) {
              +     *   releaseResources(entry.getValue());  // release resources held by entry
              +     *   return true;  // actually delete entry
              +     * }
              +     * 
              + *

              + * Alternatively, a subclass may choose to not remove the entry or selectively + * keep certain LRU entries. For example: + *

              +     * protected boolean removeLRU(LinkEntry entry) {
              +     *   if (entry.getKey().toString().startsWith("System.")) {
              +     *     return false;  // entry not removed from LRUMap
              +     *   } else {
              +     *     return true;  // actually delete entry
              +     *   }
              +     * }
              +     * 
              + * The effect of returning false is dependent on the scanUntilRemovable flag. + * If the flag is true, the next LRU entry will be passed to this method and so on + * until one returns false and is removed, or every entry in the map has been passed. + * If the scanUntilRemovable flag is false, the map will exceed the maximum size. + *

              + * NOTE: Commons Collections 3.0 passed the wrong entry to this method. + * This is fixed in version 3.1 onwards. + * + * @param entry the entry to be removed + * @return {@code true} + */ + protected boolean removeLRU(final LinkEntry entry) { + return true; + } + + //----------------------------------------------------------------------- + /** + * Returns true if this map is full and no new mappings can be added. + * + * @return true if the map is full + */ + public boolean isFull() { + return size >= maxSize; + } + + /** + * Gets the maximum size of the map (the bound). + * + * @return the maximum number of elements the map can hold + */ + public int maxSize() { + return maxSize; + } + + /** + * Whether this LRUMap will scan until a removable entry is found when the + * map is full. + * + * @return true if this map scans + * @since 3.1 + */ + public boolean isScanUntilRemovable() { + return scanUntilRemovable; + } + + //----------------------------------------------------------------------- + /** + * Clones the map without cloning the keys or values. + * + * @return a shallow clone + */ + @Override + public LRUMap clone() { + return (LRUMap) super.clone(); + } + + /** + * Write the map out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + + /** + * Read the map in using a custom routine. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + doReadObject(in); + } + + /** + * Writes the data necessary for put() to work in deserialization. + * + * @param out the output stream + * @throws IOException if an error occurs while writing to the stream + */ + @Override + protected void doWriteObject(final ObjectOutputStream out) throws IOException { + out.writeInt(maxSize); + super.doWriteObject(out); + } + + /** + * Reads the data necessary for put() to work in the superclass. + * + * @param in the input stream + * @throws IOException if an error occurs while reading from the stream + * @throws ClassNotFoundException if an object read from the stream can not be loaded + */ + @Override + protected void doReadObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + maxSize = in.readInt(); + super.doReadObject(in); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/LazyMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/LazyMap.java new file mode 100644 index 000000000..39ac2020e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/LazyMap.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Map; + +import org.apache.commons.collections4.Factory; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.functors.FactoryTransformer; + +/** + * Decorates another Map to create objects in the map on demand. + *

              + * When the {@link #get(Object)} method is called with a key that does not + * exist in the map, the factory is used to create the object. The created + * object will be added to the map using the requested key. + *

              + * For instance: + *

              + * Factory<Date> factory = new Factory<Date>() {
              + *     public Date create() {
              + *         return new Date();
              + *     }
              + * }
              + * Map<String, Date> lazy = LazyMap.lazyMap(new HashMap<String, Date>(), factory);
              + * Date date = lazy.get("NOW");
              + * 
              + * + * After the above code is executed, date will refer to + * a new Date instance. Furthermore, that Date + * instance is mapped to the "NOW" key in the map. + *

              + * Note that LazyMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: LazyMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class LazyMap extends AbstractMapDecorator implements Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 7990956402564206740L; + + /** The factory to use to construct elements */ + protected final Transformer factory; + + /** + * Factory method to create a lazily instantiated map. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param factory the factory to use, must not be null + * @return a new lazy map + * @throws NullPointerException if map or factory is null + * @since 4.0 + */ + public static LazyMap lazyMap(final Map map, final Factory< ? extends V> factory) { + return new LazyMap(map, factory); + } + + /** + * Factory method to create a lazily instantiated map. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param factory the factory to use, must not be null + * @return a new lazy map + * @throws NullPointerException if map or factory is null + * @since 4.0 + */ + public static LazyMap lazyMap(final Map map, final Transformer factory) { + return new LazyMap(map, factory); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @param factory the factory to use, must not be null + * @throws NullPointerException if map or factory is null + */ + protected LazyMap(final Map map, final Factory factory) { + super(map); + if (factory == null) { + throw new NullPointerException("Factory must not be null"); + } + this.factory = FactoryTransformer.factoryTransformer(factory); + } + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @param factory the factory to use, must not be null + * @throws NullPointerException if map or factory is null + */ + protected LazyMap(final Map map, final Transformer factory) { + super(map); + if (factory == null) { + throw new NullPointerException("Factory must not be null"); + } + this.factory = factory; + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + * @since 3.1 + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since 3.1 + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); + } + + //----------------------------------------------------------------------- + @Override + public V get(final Object key) { + // create value for key if key is not currently in the map + if (map.containsKey(key) == false) { + @SuppressWarnings("unchecked") + final K castKey = (K) key; + final V value = factory.transform(castKey); + map.put(castKey, value); + return value; + } + return map.get(key); + } + + // no need to wrap keySet, entrySet or values as they are views of + // existing map entries - you can't do a map-style get on them. +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/LazySortedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/LazySortedMap.java new file mode 100644 index 000000000..9d265e6b7 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/LazySortedMap.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.util.Comparator; +import java.util.SortedMap; + +import org.apache.commons.collections4.Factory; +import org.apache.commons.collections4.Transformer; + +/** + * Decorates another SortedMap to create objects in the map on demand. + *

              + * When the {@link #get(Object)} method is called with a key that does not + * exist in the map, the factory is used to create the object. The created + * object will be added to the map using the requested key. + *

              + * For instance: + *

              + * Factory<Date> factory = new Factory<Date>() {
              + *     public Date create() {
              + *         return new Date();
              + *     }
              + * }
              + * SortedMap<String, Date> lazy =
              + *     LazySortedMap.lazySortedMap(new HashMap<String, Date>(), factory);
              + * Date date = lazy.get("NOW");
              + * 
              + * + * After the above code is executed, date will refer to + * a new Date instance. Furthermore, that Date + * instance is mapped to the "NOW" key in the map. + *

              + * Note that LazySortedMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedSortedMap}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: LazySortedMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class LazySortedMap extends LazyMap implements SortedMap { + + /** Serialization version */ + private static final long serialVersionUID = 2715322183617658933L; + + /** + * Factory method to create a lazily instantiated sorted map. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param factory the factory to use, must not be null + * @return a new lazy sorted map + * @throws NullPointerException if map or factory is null + * @since 4.0 + */ + public static LazySortedMap lazySortedMap(final SortedMap map, + final Factory factory) { + return new LazySortedMap(map, factory); + } + + /** + * Factory method to create a lazily instantiated sorted map. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param factory the factory to use, must not be null + * @return a new lazy sorted map + * @throws NullPointerException if map or factory is null + * @since 4.0 + */ + public static LazySortedMap lazySortedMap(final SortedMap map, + final Transformer factory) { + return new LazySortedMap(map, factory); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @param factory the factory to use, must not be null + * @throws NullPointerException if map or factory is null + */ + protected LazySortedMap(final SortedMap map, final Factory factory) { + super(map, factory); + } + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @param factory the factory to use, must not be null + * @throws NullPointerException if map or factory is null + */ + protected LazySortedMap(final SortedMap map, final Transformer factory) { + super(map, factory); + } + + //----------------------------------------------------------------------- + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + protected SortedMap getSortedMap() { + return (SortedMap) map; + } + + //----------------------------------------------------------------------- + public K firstKey() { + return getSortedMap().firstKey(); + } + + public K lastKey() { + return getSortedMap().lastKey(); + } + + public Comparator comparator() { + return getSortedMap().comparator(); + } + + public SortedMap subMap(final K fromKey, final K toKey) { + final SortedMap map = getSortedMap().subMap(fromKey, toKey); + return new LazySortedMap(map, factory); + } + + public SortedMap headMap(final K toKey) { + final SortedMap map = getSortedMap().headMap(toKey); + return new LazySortedMap(map, factory); + } + + public SortedMap tailMap(final K fromKey) { + final SortedMap map = getSortedMap().tailMap(fromKey); + return new LazySortedMap(map, factory); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/LinkedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/LinkedMap.java new file mode 100644 index 000000000..924bb405c --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/LinkedMap.java @@ -0,0 +1,303 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.apache.commons.collections4.iterators.UnmodifiableIterator; +import org.apache.commons.collections4.iterators.UnmodifiableListIterator; +import org.apache.commons.collections4.list.UnmodifiableList; + +/** + * A Map implementation that maintains the order of the entries. + * In this implementation order is maintained by original insertion. + *

              + * This implementation improves on the JDK1.4 LinkedHashMap by adding the + * {@link org.apache.commons.collections4.MapIterator MapIterator} + * functionality, additional convenience methods and allowing + * bidirectional iteration. It also implements OrderedMap. + * In addition, non-interface methods are provided to access the map by index. + *

              + * The orderedMapIterator() method provides direct access to a + * bidirectional iterator. The iterators from the other views can also be cast + * to OrderedIterator if required. + *

              + * All the available iterators can be reset back to the start by casting to + * ResettableIterator and calling reset(). + *

              + * The implementation is also designed to be subclassed, with lots of useful + * methods exposed. + *

              + * Note that LinkedMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + * + * @since 3.0 + * @version $Id: LinkedMap.java 1533984 2013-10-20 21:12:51Z tn $ + */ +public class LinkedMap extends AbstractLinkedMap implements Serializable, Cloneable { + + /** Serialisation version */ + private static final long serialVersionUID = 9077234323521161066L; + + /** + * Constructs a new empty map with default size and load factor. + */ + public LinkedMap() { + super(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_THRESHOLD); + } + + /** + * Constructs a new, empty map with the specified initial capacity. + * + * @param initialCapacity the initial capacity + * @throws IllegalArgumentException if the initial capacity is negative + */ + public LinkedMap(final int initialCapacity) { + super(initialCapacity); + } + + /** + * Constructs a new, empty map with the specified initial capacity and + * load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @throws IllegalArgumentException if the initial capacity is negative + * @throws IllegalArgumentException if the load factor is less than zero + */ + public LinkedMap(final int initialCapacity, final float loadFactor) { + super(initialCapacity, loadFactor); + } + + /** + * Constructor copying elements from another map. + * + * @param map the map to copy + * @throws NullPointerException if the map is null + */ + public LinkedMap(final Map map) { + super(map); + } + + //----------------------------------------------------------------------- + /** + * Clones the map without cloning the keys or values. + * + * @return a shallow clone + */ + @Override + public LinkedMap clone() { + return (LinkedMap) super.clone(); + } + + /** + * Write the map out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + + /** + * Read the map in using a custom routine. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + doReadObject(in); + } + + //----------------------------------------------------------------------- + /** + * Gets the key at the specified index. + * + * @param index the index to retrieve + * @return the key at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + */ + public K get(final int index) { + return getEntry(index).getKey(); + } + + /** + * Gets the value at the specified index. + * + * @param index the index to retrieve + * @return the value at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + */ + public V getValue(final int index) { + return getEntry(index).getValue(); + } + + /** + * Gets the index of the specified key. + * + * @param key the key to find the index of + * @return the index, or -1 if not found + */ + public int indexOf(Object key) { + key = convertKey(key); + int i = 0; + for (LinkEntry entry = header.after; entry != header; entry = entry.after, i++) { + if (isEqualKey(key, entry.key)) { + return i; + } + } + return -1; + } + + /** + * Removes the element at the specified index. + * + * @param index the index of the object to remove + * @return the previous value corresponding the key, + * or null if none existed + * @throws IndexOutOfBoundsException if the index is invalid + */ + public V remove(final int index) { + return remove(get(index)); + } + + /** + * Gets an unmodifiable List view of the keys. + *

              + * The returned list is unmodifiable because changes to the values of + * the list (using {@link java.util.ListIterator#set(Object)}) will + * effectively remove the value from the list and reinsert that value at + * the end of the list, which is an unexpected side effect of changing the + * value of a list. This occurs because changing the key, changes when the + * mapping is added to the map and thus where it appears in the list. + *

              + * An alternative to this method is to use {@link #keySet()}. + * + * @see #keySet() + * @return The ordered list of keys. + */ + public List asList() { + return new LinkedMapList(this); + } + + /** + * List view of map. + */ + static class LinkedMapList extends AbstractList { + + private final LinkedMap parent; + + LinkedMapList(final LinkedMap parent) { + this.parent = parent; + } + + @Override + public int size() { + return parent.size(); + } + + @Override + public K get(final int index) { + return parent.get(index); + } + + @Override + public boolean contains(final Object obj) { + return parent.containsKey(obj); + } + + @Override + public int indexOf(final Object obj) { + return parent.indexOf(obj); + } + + @Override + public int lastIndexOf(final Object obj) { + return parent.indexOf(obj); + } + + @Override + public boolean containsAll(final Collection coll) { + return parent.keySet().containsAll(coll); + } + + @Override + public K remove(final int index) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object obj) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Object[] toArray() { + return parent.keySet().toArray(); + } + + @Override + public T[] toArray(final T[] array) { + return parent.keySet().toArray(array); + } + + @Override + public Iterator iterator() { + return UnmodifiableIterator.unmodifiableIterator(parent.keySet().iterator()); + } + + @Override + public ListIterator listIterator() { + return UnmodifiableListIterator.umodifiableListIterator(super.listIterator()); + } + + @Override + public ListIterator listIterator(final int fromIndex) { + return UnmodifiableListIterator.umodifiableListIterator(super.listIterator(fromIndex)); + } + + @Override + public List subList(final int fromIndexInclusive, final int toIndexExclusive) { + return UnmodifiableList.unmodifiableList(super.subList(fromIndexInclusive, toIndexExclusive)); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/ListOrderedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/ListOrderedMap.java new file mode 100644 index 000000000..df4aefca1 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/ListOrderedMap.java @@ -0,0 +1,786 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.commons.collections4.OrderedMap; +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.ResettableIterator; +import org.apache.commons.collections4.iterators.AbstractUntypedIteratorDecorator; +import org.apache.commons.collections4.keyvalue.AbstractMapEntry; +import org.apache.commons.collections4.list.UnmodifiableList; + +/** + * Decorates a Map to ensure that the order of addition is retained + * using a List to maintain order. + *

              + * The order will be used via the iterators and toArray methods on the views. + * The order is also returned by the MapIterator. + * The orderedMapIterator() method accesses an iterator that can + * iterate both forwards and backwards through the map. + * In addition, non-interface methods are provided to access the map by index. + *

              + * If an object is added to the Map for a second time, it will remain in the + * original position in the iteration. + *

              + * Note that ListOrderedMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

              + * Note that ListOrderedMap doesn't work with + * {@link java.util.IdentityHashMap IdentityHashMap}, {@link CaseInsensitiveMap}, + * or similar maps that violate the general contract of {@link java.util.Map}. + * The ListOrderedMap (or, more precisely, the underlying List) + * is relying on {@link Object#equals(Object) equals()}. This is fine, as long as the + * decorated Map is also based on {@link Object#equals(Object) equals()}, + * and {@link Object#hashCode() hashCode()}, which + * {@link java.util.IdentityHashMap IdentityHashMap}, and + * {@link CaseInsensitiveMap} don't: The former uses ==, and + * the latter uses {@link Object#equals(Object) equals()} on a lower-cased + * key. + *

              + * This class is {@link Serializable} starting with Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: ListOrderedMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class ListOrderedMap + extends AbstractMapDecorator + implements OrderedMap, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 2728177751851003750L; + + /** Internal list to hold the sequence of objects */ + private final List insertOrder = new ArrayList(); + + /** + * Factory method to create an ordered map. + *

              + * An ArrayList is used to retain order. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @return a new list ordered map + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static ListOrderedMap listOrderedMap(final Map map) { + return new ListOrderedMap(map); + } + + //----------------------------------------------------------------------- + /** + * Constructs a new empty ListOrderedMap that decorates + * a HashMap. + * + * @since 3.1 + */ + public ListOrderedMap() { + this(new HashMap()); + } + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + protected ListOrderedMap(final Map map) { + super(map); + insertOrder.addAll(decorated().keySet()); + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + * @since 3.1 + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since 3.1 + */ + @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); // (1) + } + + // Implement OrderedMap + //----------------------------------------------------------------------- + @Override + public OrderedMapIterator mapIterator() { + return new ListOrderedMapIterator(this); + } + + /** + * Gets the first key in this map by insert order. + * + * @return the first key currently in this map + * @throws NoSuchElementException if this map is empty + */ + public K firstKey() { + if (size() == 0) { + throw new NoSuchElementException("Map is empty"); + } + return insertOrder.get(0); + } + + /** + * Gets the last key in this map by insert order. + * + * @return the last key currently in this map + * @throws NoSuchElementException if this map is empty + */ + public K lastKey() { + if (size() == 0) { + throw new NoSuchElementException("Map is empty"); + } + return insertOrder.get(size() - 1); + } + + /** + * Gets the next key to the one specified using insert order. + * This method performs a list search to find the key and is O(n). + * + * @param key the key to find previous for + * @return the next key, null if no match or at start + */ + public K nextKey(final Object key) { + final int index = insertOrder.indexOf(key); + if (index >= 0 && index < size() - 1) { + return insertOrder.get(index + 1); + } + return null; + } + + /** + * Gets the previous key to the one specified using insert order. + * This method performs a list search to find the key and is O(n). + * + * @param key the key to find previous for + * @return the previous key, null if no match or at start + */ + public K previousKey(final Object key) { + final int index = insertOrder.indexOf(key); + if (index > 0) { + return insertOrder.get(index - 1); + } + return null; + } + + //----------------------------------------------------------------------- + @Override + public V put(final K key, final V value) { + if (decorated().containsKey(key)) { + // re-adding doesn't change order + return decorated().put(key, value); + } + // first add, so add to both map and list + final V result = decorated().put(key, value); + insertOrder.add(key); + return result; + } + + @Override + public void putAll(final Map map) { + for (final Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + /** + * Puts the values contained in a supplied Map into the Map starting at + * the specified index. + * + * @param index the index in the Map to start at. + * @param map the Map containing the entries to be added. + * @throws IndexOutOfBoundsException if the index is out of range [0, size] + */ + public void putAll(int index, final Map map) { + if (index < 0 || index > insertOrder.size()) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size()); + } + for (final Map.Entry entry : map.entrySet()) { + final K key = entry.getKey(); + final boolean contains = containsKey(key); + // The return value of put is null if the key did not exist OR the value was null + // so it cannot be used to determine whether the key was added + put(index, entry.getKey(), entry.getValue()); + if (!contains) { + // if no key was replaced, increment the index + index++; + } else { + // otherwise put the next item after the currently inserted key + index = indexOf(entry.getKey()) + 1; + } + } + } + + @Override + public V remove(final Object key) { + V result = null; + if (decorated().containsKey(key)) { + result = decorated().remove(key); + insertOrder.remove(key); + } + return result; + } + + @Override + public void clear() { + decorated().clear(); + insertOrder.clear(); + } + + //----------------------------------------------------------------------- + /** + * Gets a view over the keys in the map. + *

              + * The Collection will be ordered by object insertion into the map. + * + * @see #keyList() + * @return the fully modifiable collection view over the keys + */ + @Override + public Set keySet() { + return new KeySetView(this); + } + + /** + * Gets a view over the keys in the map as a List. + *

              + * The List will be ordered by object insertion into the map. + * The List is unmodifiable. + * + * @see #keySet() + * @return the unmodifiable list view over the keys + * @since 3.2 + */ + public List keyList() { + return UnmodifiableList.unmodifiableList(insertOrder); + } + + /** + * Gets a view over the values in the map. + *

              + * The Collection will be ordered by object insertion into the map. + *

              + * From Commons Collections 3.2, this Collection can be cast + * to a list, see {@link #valueList()} + * + * @see #valueList() + * @return the fully modifiable collection view over the values + */ + @Override + public Collection values() { + return new ValuesView(this); + } + + /** + * Gets a view over the values in the map as a List. + *

              + * The List will be ordered by object insertion into the map. + * The List supports remove and set, but does not support add. + * + * @see #values() + * @return the partially modifiable list view over the values + * @since 3.2 + */ + public List valueList() { + return new ValuesView(this); + } + + /** + * Gets a view over the entries in the map. + *

              + * The Set will be ordered by object insertion into the map. + * + * @return the fully modifiable set view over the entries + */ + @Override + public Set> entrySet() { + return new EntrySetView(this, this.insertOrder); + } + + //----------------------------------------------------------------------- + /** + * Returns the Map as a string. + * + * @return the Map as a String + */ + @Override + public String toString() { + if (isEmpty()) { + return "{}"; + } + final StringBuilder buf = new StringBuilder(); + buf.append('{'); + boolean first = true; + for (final Map.Entry entry : entrySet()) { + final K key = entry.getKey(); + final V value = entry.getValue(); + if (first) { + first = false; + } else { + buf.append(", "); + } + buf.append(key == this ? "(this Map)" : key); + buf.append('='); + buf.append(value == this ? "(this Map)" : value); + } + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Gets the key at the specified index. + * + * @param index the index to retrieve + * @return the key at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + */ + public K get(final int index) { + return insertOrder.get(index); + } + + /** + * Gets the value at the specified index. + * + * @param index the index to retrieve + * @return the key at the specified index + * @throws IndexOutOfBoundsException if the index is invalid + */ + public V getValue(final int index) { + return get(insertOrder.get(index)); + } + + /** + * Gets the index of the specified key. + * + * @param key the key to find the index of + * @return the index, or -1 if not found + */ + public int indexOf(final Object key) { + return insertOrder.indexOf(key); + } + + /** + * Sets the value at the specified index. + * + * @param index the index of the value to set + * @param value the new value to set + * @return the previous value at that index + * @throws IndexOutOfBoundsException if the index is invalid + * @since 3.2 + */ + public V setValue(final int index, final V value) { + final K key = insertOrder.get(index); + return put(key, value); + } + + /** + * Puts a key-value mapping into the map at the specified index. + *

              + * If the map already contains the key, then the original mapping + * is removed and the new mapping added at the specified index. + * The remove may change the effect of the index. The index is + * always calculated relative to the original state of the map. + *

              + * Thus the steps are: (1) remove the existing key-value mapping, + * then (2) insert the new key-value mapping at the position it + * would have been inserted had the remove not occurred. + * + * @param index the index at which the mapping should be inserted + * @param key the key + * @param value the value + * @return the value previously mapped to the key + * @throws IndexOutOfBoundsException if the index is out of range [0, size] + * @since 3.2 + */ + public V put(int index, final K key, final V value) { + if (index < 0 || index > insertOrder.size()) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size()); + } + + final Map m = decorated(); + if (m.containsKey(key)) { + final V result = m.remove(key); + final int pos = insertOrder.indexOf(key); + insertOrder.remove(pos); + if (pos < index) { + index--; + } + insertOrder.add(index, key); + m.put(key, value); + return result; + } + insertOrder.add(index, key); + m.put(key, value); + return null; + } + + /** + * Removes the element at the specified index. + * + * @param index the index of the object to remove + * @return the removed value, or null if none existed + * @throws IndexOutOfBoundsException if the index is invalid + */ + public V remove(final int index) { + return remove(get(index)); + } + + /** + * Gets an unmodifiable List view of the keys which changes as the map changes. + *

              + * The returned list is unmodifiable because changes to the values of + * the list (using {@link java.util.ListIterator#set(Object)}) will + * effectively remove the value from the list and reinsert that value at + * the end of the list, which is an unexpected side effect of changing the + * value of a list. This occurs because changing the key, changes when the + * mapping is added to the map and thus where it appears in the list. + *

              + * An alternative to this method is to use the better named + * {@link #keyList()} or {@link #keySet()}. + * + * @see #keyList() + * @see #keySet() + * @return The ordered list of keys. + */ + public List asList() { + return keyList(); + } + + //----------------------------------------------------------------------- + static class ValuesView extends AbstractList { + private final ListOrderedMap parent; + + @SuppressWarnings("unchecked") + ValuesView(final ListOrderedMap parent) { + super(); + this.parent = (ListOrderedMap) parent; + } + + @Override + public int size() { + return this.parent.size(); + } + + @Override + public boolean contains(final Object value) { + return this.parent.containsValue(value); + } + + @Override + public void clear() { + this.parent.clear(); + } + + @Override + public Iterator iterator() { + return new AbstractUntypedIteratorDecorator, V>(parent.entrySet().iterator()) { + public V next() { + return getIterator().next().getValue(); + } + }; + } + + @Override + public V get(final int index) { + return this.parent.getValue(index); + } + + @Override + public V set(final int index, final V value) { + return this.parent.setValue(index, value); + } + + @Override + public V remove(final int index) { + return this.parent.remove(index); + } + } + + //----------------------------------------------------------------------- + static class KeySetView extends AbstractSet { + private final ListOrderedMap parent; + + @SuppressWarnings("unchecked") + KeySetView(final ListOrderedMap parent) { + super(); + this.parent = (ListOrderedMap) parent; + } + + @Override + public int size() { + return this.parent.size(); + } + + @Override + public boolean contains(final Object value) { + return this.parent.containsKey(value); + } + + @Override + public void clear() { + this.parent.clear(); + } + + @Override + public Iterator iterator() { + return new AbstractUntypedIteratorDecorator, K>(parent.entrySet().iterator()) { + public K next() { + return getIterator().next().getKey(); + } + }; + } + } + + //----------------------------------------------------------------------- + static class EntrySetView extends AbstractSet> { + private final ListOrderedMap parent; + private final List insertOrder; + private Set> entrySet; + + public EntrySetView(final ListOrderedMap parent, final List insertOrder) { + super(); + this.parent = parent; + this.insertOrder = insertOrder; + } + + private Set> getEntrySet() { + if (entrySet == null) { + entrySet = parent.decorated().entrySet(); + } + return entrySet; + } + + @Override + public int size() { + return this.parent.size(); + } + @Override + public boolean isEmpty() { + return this.parent.isEmpty(); + } + + @Override + public boolean contains(final Object obj) { + return getEntrySet().contains(obj); + } + + @Override + public boolean containsAll(final Collection coll) { + return getEntrySet().containsAll(coll); + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + if (getEntrySet().contains(obj)) { + final Object key = ((Map.Entry) obj).getKey(); + parent.remove(key); + return true; + } + return false; + } + + @Override + public void clear() { + this.parent.clear(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + return getEntrySet().equals(obj); + } + + @Override + public int hashCode() { + return getEntrySet().hashCode(); + } + + @Override + public String toString() { + return getEntrySet().toString(); + } + + @Override + public Iterator> iterator() { + return new ListOrderedIterator(parent, insertOrder); + } + } + + //----------------------------------------------------------------------- + static class ListOrderedIterator extends AbstractUntypedIteratorDecorator> { + private final ListOrderedMap parent; + private K last = null; + + ListOrderedIterator(final ListOrderedMap parent, final List insertOrder) { + super(insertOrder.iterator()); + this.parent = parent; + } + + public Map.Entry next() { + last = getIterator().next(); + return new ListOrderedMapEntry(parent, last); + } + + @Override + public void remove() { + super.remove(); + parent.decorated().remove(last); + } + } + + //----------------------------------------------------------------------- + static class ListOrderedMapEntry extends AbstractMapEntry { + private final ListOrderedMap parent; + + ListOrderedMapEntry(final ListOrderedMap parent, final K key) { + super(key, null); + this.parent = parent; + } + + @Override + public V getValue() { + return parent.get(getKey()); + } + + @Override + public V setValue(final V value) { + return parent.decorated().put(getKey(), value); + } + } + + //----------------------------------------------------------------------- + static class ListOrderedMapIterator implements OrderedMapIterator, ResettableIterator { + private final ListOrderedMap parent; + private ListIterator iterator; + private K last = null; + private boolean readable = false; + + ListOrderedMapIterator(final ListOrderedMap parent) { + super(); + this.parent = parent; + this.iterator = parent.insertOrder.listIterator(); + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public K next() { + last = iterator.next(); + readable = true; + return last; + } + + public boolean hasPrevious() { + return iterator.hasPrevious(); + } + + public K previous() { + last = iterator.previous(); + readable = true; + return last; + } + + public void remove() { + if (readable == false) { + throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); + } + iterator.remove(); + parent.map.remove(last); + readable = false; + } + + public K getKey() { + if (readable == false) { + throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); + } + return last; + } + + public V getValue() { + if (readable == false) { + throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); + } + return parent.get(last); + } + + public V setValue(final V value) { + if (readable == false) { + throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); + } + return parent.map.put(last, value); + } + + public void reset() { + iterator = parent.insertOrder.listIterator(); + last = null; + readable = false; + } + + @Override + public String toString() { + if (readable == true) { + return "Iterator[" + getKey() + "=" + getValue() + "]"; + } + return "Iterator[]"; + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/MultiKeyMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/MultiKeyMap.java new file mode 100644 index 000000000..849f293ff --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/MultiKeyMap.java @@ -0,0 +1,908 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Map; + +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.keyvalue.MultiKey; + +/** + * A Map implementation that uses multiple keys to map the value. + *

              + * This class is the most efficient way to uses multiple keys to map to a value. + * The best way to use this class is via the additional map-style methods. + * These provide get, containsKey, put and + * remove for individual keys which operate without extra object creation. + *

              + * The additional methods are the main interface of this map. + * As such, you will not normally hold this map in a variable of type Map. + *

              + * The normal map methods take in and return a {@link MultiKey}. + * If you try to use put() with any other object type a + * ClassCastException is thrown. If you try to use null as + * the key in put() a NullPointerException is thrown. + *

              + * This map is implemented as a decorator of a AbstractHashedMap which + * enables extra behaviour to be added easily. + *

                + *
              • MultiKeyMap.decorate(new LinkedMap()) creates an ordered map. + *
              • MultiKeyMap.decorate(new LRUMap()) creates an least recently used map. + *
              • MultiKeyMap.decorate(new ReferenceMap()) creates a garbage collector sensitive map. + *
              + * Note that IdentityMap and ReferenceIdentityMap are unsuitable + * for use as the key comparison would work on the whole MultiKey, not the elements within. + *

              + * As an example, consider a least recently used cache that uses a String airline code + * and a Locale to lookup the airline's name: + *

              + * private MultiKeyMap cache = MultiKeyMap.multiKeyMap(new LRUMap(50));
              + *
              + * public String getAirlineName(String code, String locale) {
              + *   String name = (String) cache.get(code, locale);
              + *   if (name == null) {
              + *     name = getAirlineNameFromDB(code, locale);
              + *     cache.put(code, locale, name);
              + *   }
              + *   return name;
              + * }
              + * 
              + *

              + * Note that MultiKeyMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. This class may throw exceptions when accessed + * by concurrent threads without synchronization. + * + * @since 3.1 + * @version $Id: MultiKeyMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class MultiKeyMap extends AbstractMapDecorator, V> + implements Serializable, Cloneable { + + /** Serialisation version */ + private static final long serialVersionUID = -1788199231038721040L; + + //----------------------------------------------------------------------- + /** + * Decorates the specified map to add the MultiKeyMap API and fast query. + * The map must not be null and must be empty. + * + * @param the key type + * @param the value type + * @param map the map to decorate, not null + * @return a new multi key map + * @throws NullPointerException if map is null + * @throws IllegalArgumentException if the map is not empty + * @since 4.0 + */ + public static MultiKeyMap multiKeyMap(final AbstractHashedMap, V> map) { + if (map == null) { + throw new NullPointerException("Map must not be null"); + } + if (map.size() > 0) { + throw new IllegalArgumentException("Map must be empty"); + } + return new MultiKeyMap(map); + } + + //----------------------------------------------------------------------- + /** + * Constructs a new MultiKeyMap that decorates a HashedMap. + */ + public MultiKeyMap() { + this(new HashedMap, V>()); + } + + /** + * Constructor that decorates the specified map and is called from + * {@link #multiKeyMap(AbstractHashedMap)}. + * The map must not be null and should be empty or only contain valid keys. + * This constructor performs no validation. + * + * @param map the map to decorate + */ + protected MultiKeyMap(final AbstractHashedMap, V> map) { + super(map); + this.map = map; + } + + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @return the mapped value, null if no match + */ + public V get(final Object key1, final Object key2) { + final int hashCode = hash(key1, key2); + AbstractHashedMap.HashEntry, V> entry = + decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) { + return entry.getValue(); + } + entry = entry.next; + } + return null; + } + + /** + * Checks whether the map contains the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @return true if the map contains the key + */ + public boolean containsKey(final Object key1, final Object key2) { + final int hashCode = hash(key1, key2); + AbstractHashedMap.HashEntry, V> entry = + decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) { + return true; + } + entry = entry.next; + } + return false; + } + + /** + * Stores the value against the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param value the value to store + * @return the value previously mapped to this combined key, null if none + */ + public V put(final K key1, final K key2, final V value) { + final int hashCode = hash(key1, key2); + final int index = decorated().hashIndex(hashCode, decorated().data.length); + AbstractHashedMap.HashEntry, V> entry = decorated().data[index]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) { + final V oldValue = entry.getValue(); + decorated().updateEntry(entry, value); + return oldValue; + } + entry = entry.next; + } + decorated().addMapping(index, hashCode, new MultiKey(key1, key2), value); + return null; + } + + /** + * Removes the specified multi-key from this map. + * + * @param key1 the first key + * @param key2 the second key + * @return the value mapped to the removed key, null if key not in map + * @since 4.0 (previous name: remove(Object, Object)) + */ + public V removeMultiKey(final Object key1, final Object key2) { + final int hashCode = hash(key1, key2); + final int index = decorated().hashIndex(hashCode, decorated().data.length); + AbstractHashedMap.HashEntry, V> entry = decorated().data[index]; + AbstractHashedMap.HashEntry, V> previous = null; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) { + final V oldValue = entry.getValue(); + decorated().removeMapping(entry, index, previous); + return oldValue; + } + previous = entry; + entry = entry.next; + } + return null; + } + + /** + * Gets the hash code for the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @return the hash code + */ + protected int hash(final Object key1, final Object key2) { + int h = 0; + if (key1 != null) { + h ^= key1.hashCode(); + } + if (key2 != null) { + h ^= key2.hashCode(); + } + h += ~(h << 9); + h ^= h >>> 14; + h += h << 4; + h ^= h >>> 10; + return h; + } + + /** + * Is the key equal to the combined key. + * + * @param entry the entry to compare to + * @param key1 the first key + * @param key2 the second key + * @return true if the key matches + */ + protected boolean isEqualKey(final AbstractHashedMap.HashEntry, V> entry, + final Object key1, final Object key2) { + final MultiKey multi = entry.getKey(); + return + multi.size() == 2 && + (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) && + (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))); + } + + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @return the mapped value, null if no match + */ + public V get(final Object key1, final Object key2, final Object key3) { + final int hashCode = hash(key1, key2, key3); + AbstractHashedMap.HashEntry, V> entry = + decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) { + return entry.getValue(); + } + entry = entry.next; + } + return null; + } + + /** + * Checks whether the map contains the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @return true if the map contains the key + */ + public boolean containsKey(final Object key1, final Object key2, final Object key3) { + final int hashCode = hash(key1, key2, key3); + AbstractHashedMap.HashEntry, V> entry = + decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) { + return true; + } + entry = entry.next; + } + return false; + } + + /** + * Stores the value against the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param value the value to store + * @return the value previously mapped to this combined key, null if none + */ + public V put(final K key1, final K key2, final K key3, final V value) { + final int hashCode = hash(key1, key2, key3); + final int index = decorated().hashIndex(hashCode, decorated().data.length); + AbstractHashedMap.HashEntry, V> entry = decorated().data[index]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) { + final V oldValue = entry.getValue(); + decorated().updateEntry(entry, value); + return oldValue; + } + entry = entry.next; + } + decorated().addMapping(index, hashCode, new MultiKey(key1, key2, key3), value); + return null; + } + + /** + * Removes the specified multi-key from this map. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @return the value mapped to the removed key, null if key not in map + * @since 4.0 (previous name: remove(Object, Object, Object)) + */ + public V removeMultiKey(final Object key1, final Object key2, final Object key3) { + final int hashCode = hash(key1, key2, key3); + final int index = decorated().hashIndex(hashCode, decorated().data.length); + AbstractHashedMap.HashEntry, V> entry = decorated().data[index]; + AbstractHashedMap.HashEntry, V> previous = null; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) { + final V oldValue = entry.getValue(); + decorated().removeMapping(entry, index, previous); + return oldValue; + } + previous = entry; + entry = entry.next; + } + return null; + } + + /** + * Gets the hash code for the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @return the hash code + */ + protected int hash(final Object key1, final Object key2, final Object key3) { + int h = 0; + if (key1 != null) { + h ^= key1.hashCode(); + } + if (key2 != null) { + h ^= key2.hashCode(); + } + if (key3 != null) { + h ^= key3.hashCode(); + } + h += ~(h << 9); + h ^= h >>> 14; + h += h << 4; + h ^= h >>> 10; + return h; + } + + /** + * Is the key equal to the combined key. + * + * @param entry the entry to compare to + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @return true if the key matches + */ + protected boolean isEqualKey(final AbstractHashedMap.HashEntry, V> entry, + final Object key1, final Object key2, final Object key3) { + final MultiKey multi = entry.getKey(); + return + multi.size() == 3 && + (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) && + (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))) && + (key3 == multi.getKey(2) || key3 != null && key3.equals(multi.getKey(2))); + } + + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @return the mapped value, null if no match + */ + public V get(final Object key1, final Object key2, final Object key3, final Object key4) { + final int hashCode = hash(key1, key2, key3, key4); + AbstractHashedMap.HashEntry, V> entry = + decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) { + return entry.getValue(); + } + entry = entry.next; + } + return null; + } + + /** + * Checks whether the map contains the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @return true if the map contains the key + */ + public boolean containsKey(final Object key1, final Object key2, final Object key3, final Object key4) { + final int hashCode = hash(key1, key2, key3, key4); + AbstractHashedMap.HashEntry, V> entry = + decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) { + return true; + } + entry = entry.next; + } + return false; + } + + /** + * Stores the value against the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @param value the value to store + * @return the value previously mapped to this combined key, null if none + */ + public V put(final K key1, final K key2, final K key3, final K key4, final V value) { + final int hashCode = hash(key1, key2, key3, key4); + final int index = decorated().hashIndex(hashCode, decorated().data.length); + AbstractHashedMap.HashEntry, V> entry = decorated().data[index]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) { + final V oldValue = entry.getValue(); + decorated().updateEntry(entry, value); + return oldValue; + } + entry = entry.next; + } + decorated().addMapping(index, hashCode, new MultiKey(key1, key2, key3, key4), value); + return null; + } + + /** + * Removes the specified multi-key from this map. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @return the value mapped to the removed key, null if key not in map + * @since 4.0 (previous name: remove(Object, Object, Object, Object)) + */ + public V removeMultiKey(final Object key1, final Object key2, final Object key3, final Object key4) { + final int hashCode = hash(key1, key2, key3, key4); + final int index = decorated().hashIndex(hashCode, decorated().data.length); + AbstractHashedMap.HashEntry, V> entry = decorated().data[index]; + AbstractHashedMap.HashEntry, V> previous = null; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) { + final V oldValue = entry.getValue(); + decorated().removeMapping(entry, index, previous); + return oldValue; + } + previous = entry; + entry = entry.next; + } + return null; + } + + /** + * Gets the hash code for the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @return the hash code + */ + protected int hash(final Object key1, final Object key2, final Object key3, final Object key4) { + int h = 0; + if (key1 != null) { + h ^= key1.hashCode(); + } + if (key2 != null) { + h ^= key2.hashCode(); + } + if (key3 != null) { + h ^= key3.hashCode(); + } + if (key4 != null) { + h ^= key4.hashCode(); + } + h += ~(h << 9); + h ^= h >>> 14; + h += h << 4; + h ^= h >>> 10; + return h; + } + + /** + * Is the key equal to the combined key. + * + * @param entry the entry to compare to + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @return true if the key matches + */ + protected boolean isEqualKey(final AbstractHashedMap.HashEntry, V> entry, + final Object key1, final Object key2, final Object key3, final Object key4) { + final MultiKey multi = entry.getKey(); + return + multi.size() == 4 && + (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) && + (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))) && + (key3 == multi.getKey(2) || key3 != null && key3.equals(multi.getKey(2))) && + (key4 == multi.getKey(3) || key4 != null && key4.equals(multi.getKey(3))); + } + + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @param key5 the fifth key + * @return the mapped value, null if no match + */ + public V get(final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) { + final int hashCode = hash(key1, key2, key3, key4, key5); + AbstractHashedMap.HashEntry, V> entry = + decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) { + return entry.getValue(); + } + entry = entry.next; + } + return null; + } + + /** + * Checks whether the map contains the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @param key5 the fifth key + * @return true if the map contains the key + */ + public boolean containsKey(final Object key1, final Object key2, final Object key3, + final Object key4, final Object key5) { + final int hashCode = hash(key1, key2, key3, key4, key5); + AbstractHashedMap.HashEntry, V> entry = + decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) { + return true; + } + entry = entry.next; + } + return false; + } + + /** + * Stores the value against the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @param key5 the fifth key + * @param value the value to store + * @return the value previously mapped to this combined key, null if none + */ + public V put(final K key1, final K key2, final K key3, final K key4, final K key5, final V value) { + final int hashCode = hash(key1, key2, key3, key4, key5); + final int index = decorated().hashIndex(hashCode, decorated().data.length); + AbstractHashedMap.HashEntry, V> entry = decorated().data[index]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) { + final V oldValue = entry.getValue(); + decorated().updateEntry(entry, value); + return oldValue; + } + entry = entry.next; + } + decorated().addMapping(index, hashCode, new MultiKey(key1, key2, key3, key4, key5), value); + return null; + } + + /** + * Removes the specified multi-key from this map. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @param key5 the fifth key + * @return the value mapped to the removed key, null if key not in map + * @since 4.0 (previous name: remove(Object, Object, Object, Object, Object)) + */ + public V removeMultiKey(final Object key1, final Object key2, final Object key3, + final Object key4, final Object key5) { + final int hashCode = hash(key1, key2, key3, key4, key5); + final int index = decorated().hashIndex(hashCode, decorated().data.length); + AbstractHashedMap.HashEntry, V> entry = decorated().data[index]; + AbstractHashedMap.HashEntry, V> previous = null; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) { + final V oldValue = entry.getValue(); + decorated().removeMapping(entry, index, previous); + return oldValue; + } + previous = entry; + entry = entry.next; + } + return null; + } + + /** + * Gets the hash code for the specified multi-key. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @param key5 the fifth key + * @return the hash code + */ + protected int hash(final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) { + int h = 0; + if (key1 != null) { + h ^= key1.hashCode(); + } + if (key2 != null) { + h ^= key2.hashCode(); + } + if (key3 != null) { + h ^= key3.hashCode(); + } + if (key4 != null) { + h ^= key4.hashCode(); + } + if (key5 != null) { + h ^= key5.hashCode(); + } + h += ~(h << 9); + h ^= h >>> 14; + h += h << 4; + h ^= h >>> 10; + return h; + } + + /** + * Is the key equal to the combined key. + * + * @param entry the entry to compare to + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @param key5 the fifth key + * @return true if the key matches + */ + protected boolean isEqualKey(final AbstractHashedMap.HashEntry, V> entry, + final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) { + final MultiKey multi = entry.getKey(); + return + multi.size() == 5 && + (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) && + (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))) && + (key3 == multi.getKey(2) || key3 != null && key3.equals(multi.getKey(2))) && + (key4 == multi.getKey(3) || key4 != null && key4.equals(multi.getKey(3))) && + (key5 == multi.getKey(4) || key5 != null && key5.equals(multi.getKey(4))); + } + + //----------------------------------------------------------------------- + /** + * Removes all mappings where the first key is that specified. + *

              + * This method removes all the mappings where the MultiKey + * has one or more keys, and the first matches that specified. + * + * @param key1 the first key + * @return true if any elements were removed + */ + public boolean removeAll(final Object key1) { + boolean modified = false; + final MapIterator, V> it = mapIterator(); + while (it.hasNext()) { + final MultiKey multi = it.next(); + if (multi.size() >= 1 && + (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0)))) { + it.remove(); + modified = true; + } + } + return modified; + } + + /** + * Removes all mappings where the first two keys are those specified. + *

              + * This method removes all the mappings where the MultiKey + * has two or more keys, and the first two match those specified. + * + * @param key1 the first key + * @param key2 the second key + * @return true if any elements were removed + */ + public boolean removeAll(final Object key1, final Object key2) { + boolean modified = false; + final MapIterator, V> it = mapIterator(); + while (it.hasNext()) { + final MultiKey multi = it.next(); + if (multi.size() >= 2 && + (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) && + (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1)))) { + it.remove(); + modified = true; + } + } + return modified; + } + + /** + * Removes all mappings where the first three keys are those specified. + *

              + * This method removes all the mappings where the MultiKey + * has three or more keys, and the first three match those specified. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @return true if any elements were removed + */ + public boolean removeAll(final Object key1, final Object key2, final Object key3) { + boolean modified = false; + final MapIterator, V> it = mapIterator(); + while (it.hasNext()) { + final MultiKey multi = it.next(); + if (multi.size() >= 3 && + (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) && + (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) && + (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2)))) { + it.remove(); + modified = true; + } + } + return modified; + } + + /** + * Removes all mappings where the first four keys are those specified. + *

              + * This method removes all the mappings where the MultiKey + * has four or more keys, and the first four match those specified. + * + * @param key1 the first key + * @param key2 the second key + * @param key3 the third key + * @param key4 the fourth key + * @return true if any elements were removed + */ + public boolean removeAll(final Object key1, final Object key2, final Object key3, final Object key4) { + boolean modified = false; + final MapIterator, V> it = mapIterator(); + while (it.hasNext()) { + final MultiKey multi = it.next(); + if (multi.size() >= 4 && + (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) && + (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) && + (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2))) && + (key4 == null ? multi.getKey(3) == null : key4.equals(multi.getKey(3)))) { + it.remove(); + modified = true; + } + } + return modified; + } + + //----------------------------------------------------------------------- + /** + * Check to ensure that input keys are valid MultiKey objects. + * + * @param key the key to check + */ + protected void checkKey(final MultiKey key) { + if (key == null) { + throw new NullPointerException("Key must not be null"); + } + } + + /** + * Clones the map without cloning the keys or values. + * + * @return a shallow clone + */ + @SuppressWarnings("unchecked") + @Override + public MultiKeyMap clone() { + try { + return (MultiKeyMap) super.clone(); + } catch (final CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Puts the key and value into the map, where the key must be a non-null + * MultiKey object. + * + * @param key the non-null MultiKey object + * @param value the value to store + * @return the previous value for the key + * @throws NullPointerException if the key is null + * @throws ClassCastException if the key is not a MultiKey + */ + @Override + public V put(final MultiKey key, final V value) { + checkKey(key); + return super.put(key, value); + } + + /** + * Copies all of the keys and values from the specified map to this map. + * Each key must be non-null and a MultiKey object. + * + * @param mapToCopy to this map + * @throws NullPointerException if the mapToCopy or any key within is null + * @throws ClassCastException if any key in mapToCopy is not a MultiKey + */ + @Override + public void putAll(final Map, ? extends V> mapToCopy) { + for (final MultiKey key : mapToCopy.keySet()) { + checkKey(key); + } + super.putAll(mapToCopy); + } + + //----------------------------------------------------------------------- + @Override + public MapIterator, V> mapIterator() { + return decorated().mapIterator(); + } + + /** + * {@inheritDoc} + */ + @Override + protected AbstractHashedMap, V> decorated() { + return (AbstractHashedMap, V>) super.decorated(); + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map, V>) in.readObject(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/MultiValueMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/MultiValueMap.java new file mode 100644 index 000000000..fbcedd64a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/MultiValueMap.java @@ -0,0 +1,577 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Factory; +import org.apache.commons.collections4.FunctorException; +import org.apache.commons.collections4.MultiMap; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.iterators.EmptyIterator; +import org.apache.commons.collections4.iterators.IteratorChain; +import org.apache.commons.collections4.iterators.LazyIteratorChain; +import org.apache.commons.collections4.iterators.TransformIterator; + +/** + * A MultiValueMap decorates another map, allowing it to have + * more than one value for a key. + *

              + * A MultiMap is a Map with slightly different semantics. + * Putting a value into the map will add the value to a Collection at that key. + * Getting a value will return a Collection, holding all the values put to that key. + *

              + * This implementation is a decorator, allowing any Map implementation + * to be used as the base. + *

              + * In addition, this implementation allows the type of collection used + * for the values to be controlled. By default, an ArrayList + * is used, however a Class to instantiate may be specified, + * or a factory that returns a Collection instance. + *

              + * Note that MultiValueMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. This class may throw exceptions when accessed + * by concurrent threads without synchronization. + * + * @since 3.2 + * @version $Id: MultiValueMap.java 1714360 2015-11-14 20:24:42Z tn $ + * @deprecated since 4.1, use {@link org.apache.commons.collections4.MultiValuedMap MultiValuedMap} instead + */ +@Deprecated +public class MultiValueMap extends AbstractMapDecorator implements MultiMap, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = -2214159910087182007L; + + /** The factory for creating value collections. */ + private final Factory> collectionFactory; + /** The cached values. */ + private transient Collection valuesView; + + /** + * Creates a map which wraps the given map and + * maps keys to ArrayLists. + * + * @param the key type + * @param the value type + * @param map the map to wrap + * @return a new multi-value map + * @since 4.0 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static MultiValueMap multiValueMap(final Map> map) { + return MultiValueMap. multiValueMap((Map) map, ArrayList.class); + } + + /** + * Creates a map which decorates the given map and + * maps keys to collections of type collectionClass. + * + * @param the key type + * @param the value type + * @param the collection class type + * @param map the map to wrap + * @param collectionClass the type of the collection class + * @return a new multi-value map + * @since 4.0 + */ + public static > MultiValueMap multiValueMap(final Map map, + final Class collectionClass) { + return new MultiValueMap(map, new ReflectionFactory(collectionClass)); + } + + /** + * Creates a map which decorates the given map and + * creates the value collections using the supplied collectionFactory. + * + * @param the key type + * @param the value type + * @param the collection class type + * @param map the map to decorate + * @param collectionFactory the collection factory (must return a Collection object). + * @return a new multi-value map + * @since 4.0 + */ + public static > MultiValueMap multiValueMap(final Map map, + final Factory collectionFactory) { + return new MultiValueMap(map, collectionFactory); + } + + //----------------------------------------------------------------------- + /** + * Creates a MultiValueMap based on a HashMap and + * storing the multiple values in an ArrayList. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public MultiValueMap() { + this(new HashMap(), new ReflectionFactory(ArrayList.class)); + } + + /** + * Creates a MultiValueMap which decorates the given map and + * creates the value collections using the supplied collectionFactory. + * + * @param the collection class type + * @param map the map to decorate + * @param collectionFactory the collection factory which must return a Collection instance + */ + @SuppressWarnings("unchecked") + protected > MultiValueMap(final Map map, + final Factory collectionFactory) { + super((Map) map); + if (collectionFactory == null) { + throw new IllegalArgumentException("The factory must not be null"); + } + this.collectionFactory = collectionFactory; + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + * @since 4.0 + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since 4.0 + */ + @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); // (1) + } + + //----------------------------------------------------------------------- + /** + * Clear the map. + */ + @Override + public void clear() { + // If you believe that you have GC issues here, try uncommenting this code +// Set pairs = getMap().entrySet(); +// Iterator pairsIterator = pairs.iterator(); +// while (pairsIterator.hasNext()) { +// Map.Entry keyValuePair = (Map.Entry) pairsIterator.next(); +// Collection coll = (Collection) keyValuePair.getValue(); +// coll.clear(); +// } + decorated().clear(); + } + + /** + * Removes a specific value from map. + *

              + * The item is removed from the collection mapped to the specified key. + * Other values attached to that key are unaffected. + *

              + * If the last value for a key is removed, null will be returned + * from a subsequent get(key). + * + * @param key the key to remove from + * @param value the value to remove + * @return {@code true} if the mapping was removed, {@code false} otherwise + */ + @Override + public boolean removeMapping(final Object key, final Object value) { + final Collection valuesForKey = getCollection(key); + if (valuesForKey == null) { + return false; + } + final boolean removed = valuesForKey.remove(value); + if (removed == false) { + return false; + } + if (valuesForKey.isEmpty()) { + remove(key); + } + return true; + } + + /** + * Checks whether the map contains the value specified. + *

              + * This checks all collections against all keys for the value, and thus could be slow. + * + * @param value the value to search for + * @return true if the map contains the value + */ + @Override + @SuppressWarnings("unchecked") + public boolean containsValue(final Object value) { + final Set> pairs = decorated().entrySet(); + if (pairs != null) { + for (final Map.Entry entry : pairs) { + if (((Collection) entry.getValue()).contains(value)) { + return true; + } + } + } + return false; + } + + /** + * Adds the value to the collection associated with the specified key. + *

              + * Unlike a normal Map the previous value is not replaced. + * Instead the new value is added to the collection stored against the key. + * + * @param key the key to store against + * @param value the value to add to the collection at the key + * @return the value added if the map changed and null if the map did not change + */ + @Override + @SuppressWarnings("unchecked") + public Object put(final K key, final Object value) { + boolean result = false; + Collection coll = getCollection(key); + if (coll == null) { + coll = createCollection(1); // might produce a non-empty collection + coll.add((V) value); + if (coll.size() > 0) { + // only add if non-zero size to maintain class state + decorated().put(key, coll); + result = true; // map definitely changed + } + } else { + result = coll.add((V) value); + } + return result ? value : null; + } + + /** + * Override superclass to ensure that MultiMap instances are + * correctly handled. + *

              + * If you call this method with a normal map, each entry is + * added using put(Object,Object). + * If you call this method with a multi map, each entry is + * added using putAll(Object,Collection). + * + * @param map the map to copy (either a normal or multi map) + */ + @Override + @SuppressWarnings("unchecked") + public void putAll(final Map map) { + if (map instanceof MultiMap) { + for (final Map.Entry entry : ((MultiMap) map).entrySet()) { + putAll(entry.getKey(), (Collection) entry.getValue()); + } + } else { + for (final Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + } + + /** + * {@inheritDoc} + *

              + * NOTE: the returned Entry objects will contain as value a {@link Collection} + * of all values that are mapped to the given key. To get a "flattened" version + * of all mappings contained in this map, use {@link #iterator()}. + * + * @see #iterator() + */ + @Override + public Set> entrySet() { + return super.entrySet(); + } + + /** + * Gets a collection containing all the values in the map. + *

              + * This returns a collection containing the combination of values from all keys. + * + * @return a collection view of the values contained in this map + */ + @Override + @SuppressWarnings("unchecked") + public Collection values() { + final Collection vs = valuesView; + return (Collection) (vs != null ? vs : (valuesView = new Values())); + } + + /** + * Checks whether the collection at the specified key contains the value. + * + * @param key the key to search for + * @param value the value to search for + * @return true if the map contains the value + */ + public boolean containsValue(final Object key, final Object value) { + final Collection coll = getCollection(key); + if (coll == null) { + return false; + } + return coll.contains(value); + } + + /** + * Gets the collection mapped to the specified key. + * This method is a convenience method to typecast the result of get(key). + * + * @param key the key to retrieve + * @return the collection mapped to the key, null if no mapping + */ + @SuppressWarnings("unchecked") + public Collection getCollection(final Object key) { + return (Collection) decorated().get(key); + } + + /** + * Gets the size of the collection mapped to the specified key. + * + * @param key the key to get size for + * @return the size of the collection at the key, zero if key not in map + */ + public int size(final Object key) { + final Collection coll = getCollection(key); + if (coll == null) { + return 0; + } + return coll.size(); + } + + /** + * Adds a collection of values to the collection associated with + * the specified key. + * + * @param key the key to store against + * @param values the values to add to the collection at the key, null ignored + * @return true if this map changed + */ + public boolean putAll(final K key, final Collection values) { + if (values == null || values.size() == 0) { + return false; + } + boolean result = false; + Collection coll = getCollection(key); + if (coll == null) { + coll = createCollection(values.size()); // might produce a non-empty collection + coll.addAll(values); + if (coll.size() > 0) { + // only add if non-zero size to maintain class state + decorated().put(key, coll); + result = true; // map definitely changed + } + } else { + result = coll.addAll(values); + } + return result; + } + + /** + * Gets an iterator for the collection mapped to the specified key. + * + * @param key the key to get an iterator for + * @return the iterator of the collection at the key, empty iterator if key not in map + */ + public Iterator iterator(final Object key) { + if (!containsKey(key)) { + return EmptyIterator.emptyIterator(); + } + return new ValuesIterator(key); + } + + /** + * Gets an iterator for all mappings stored in this {@link MultiValueMap}. + *

              + * The iterator will return multiple Entry objects with the same key + * if there are multiple values mapped to this key. + *

              + * NOTE: calling {@link java.util.Map.Entry#setValue(Object)} on any of the returned + * elements will result in a {@link UnsupportedOperationException}. + * + * @return the iterator of all mappings in this map + * @since 4.0 + */ + public Iterator> iterator() { + final Collection allKeys = new ArrayList(keySet()); + final Iterator keyIterator = allKeys.iterator(); + + return new LazyIteratorChain>() { + @Override + protected Iterator> nextIterator(int count) { + if ( ! keyIterator.hasNext() ) { + return null; + } + final K key = keyIterator.next(); + final Transformer> transformer = new Transformer>() { + @Override + public Entry transform(final V input) { + return new Entry() { + @Override + public K getKey() { + return key; + } + @Override + public V getValue() { + return input; + } + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + }; + } + }; + return new TransformIterator>(new ValuesIterator(key), transformer); + } + }; + } + + /** + * Gets the total size of the map by counting all the values. + * + * @return the total size of the map counting all values + */ + public int totalSize() { + int total = 0; + for (final Object v : decorated().values()) { + total += CollectionUtils.size(v); + } + return total; + } + + /** + * Creates a new instance of the map value Collection container + * using the factory. + *

              + * This method can be overridden to perform your own processing + * instead of using the factory. + * + * @param size the collection size that is about to be added + * @return the new collection + */ + protected Collection createCollection(final int size) { + return collectionFactory.create(); + } + + //----------------------------------------------------------------------- + /** + * Inner class that provides the values view. + */ + private class Values extends AbstractCollection { + @Override + public Iterator iterator() { + final IteratorChain chain = new IteratorChain(); + for (final K k : keySet()) { + chain.addIterator(new ValuesIterator(k)); + } + return chain; + } + + @Override + public int size() { + return totalSize(); + } + + @Override + public void clear() { + MultiValueMap.this.clear(); + } + } + + /** + * Inner class that provides the values iterator. + */ + private class ValuesIterator implements Iterator { + private final Object key; + private final Collection values; + private final Iterator iterator; + + public ValuesIterator(final Object key) { + this.key = key; + this.values = getCollection(key); + this.iterator = values.iterator(); + } + + @Override + public void remove() { + iterator.remove(); + if (values.isEmpty()) { + MultiValueMap.this.remove(key); + } + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public V next() { + return iterator.next(); + } + } + + /** + * Inner class that provides a simple reflection factory. + */ + private static class ReflectionFactory> implements Factory, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 2986114157496788874L; + + private final Class clazz; + + public ReflectionFactory(final Class clazz) { + this.clazz = clazz; + } + + @Override + public T create() { + try { + return clazz.newInstance(); + } catch (final Exception ex) { + throw new FunctorException("Cannot instantiate class: " + clazz, ex); + } + } + + private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException { + is.defaultReadObject(); + // ensure that the de-serialized class is a Collection, COLLECTIONS-580 + if (clazz != null && !Collection.class.isAssignableFrom(clazz)) { + throw new UnsupportedOperationException(); + } + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/PassiveExpiringMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/PassiveExpiringMap.java new file mode 100644 index 000000000..647c8d089 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/PassiveExpiringMap.java @@ -0,0 +1,538 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * Decorates a Map to evict expired entries once their expiration + * time has been reached. + *

              + * When putting a key-value pair in the map this decorator uses a + * {@link ExpirationPolicy} to determine how long the entry should remain alive + * as defined by an expiration time value. + *

              + *

              + * When accessing the mapped value for a key, its expiration time is checked, + * and if it is a negative value or if it is greater than the current time, the + * mapped value is returned. Otherwise, the key is removed from the decorated + * map, and null is returned. + *

              + *

              + * When invoking methods that involve accessing the entire map contents (i.e + * {@link #containsKey(Object)}, {@link #entrySet()}, etc.) this decorator + * removes all expired entries prior to actually completing the invocation. + *

              + *

              + * Note that {@link PassiveExpiringMap} is not synchronized and is not + * thread-safe. If you wish to use this map from multiple threads + * concurrently, you must use appropriate synchronization. The simplest approach + * is to wrap this map using {@link java.util.Collections#synchronizedMap(Map)}. + * This class may throw exceptions when accessed by concurrent threads without + * synchronization. + *

              + * + * @param the type of the keys in the map + * @param the type of the values in the map + * @since 4.0 + * @version $Id: PassiveExpiringMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PassiveExpiringMap + extends AbstractMapDecorator + implements Serializable { + + /** + * A {@link org.apache.commons.collections4.map.PassiveExpiringMap.ExpirationPolicy ExpirationPolicy} + * that returns a expiration time that is a + * constant about of time in the future from the current time. + * + * @param the type of the keys in the map + * @param the type of the values in the map + * @since 4.0 + * @version $Id: PassiveExpiringMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ + public static class ConstantTimeToLiveExpirationPolicy + implements ExpirationPolicy { + + /** Serialization version */ + private static final long serialVersionUID = 1L; + + /** the constant time-to-live value measured in milliseconds. */ + private final long timeToLiveMillis; + + /** + * Default constructor. Constructs a policy using a negative + * time-to-live value that results in entries never expiring. + */ + public ConstantTimeToLiveExpirationPolicy() { + this(-1L); + } + + /** + * Construct a policy with the given time-to-live constant measured in + * milliseconds. A negative time-to-live value indicates entries never + * expire. A zero time-to-live value indicates entries expire (nearly) + * immediately. + * + * @param timeToLiveMillis the constant amount of time (in milliseconds) + * an entry is available before it expires. A negative value + * results in entries that NEVER expire. A zero value results in + * entries that ALWAYS expire. + */ + public ConstantTimeToLiveExpirationPolicy(final long timeToLiveMillis) { + super(); + this.timeToLiveMillis = timeToLiveMillis; + } + + /** + * Construct a policy with the given time-to-live constant measured in + * the given time unit of measure. + * + * @param timeToLive the constant amount of time an entry is available + * before it expires. A negative value results in entries that + * NEVER expire. A zero value results in entries that ALWAYS + * expire. + * @param timeUnit the unit of time for the timeToLive + * parameter, must not be null. + * @throws NullPointerException if the time unit is null. + */ + public ConstantTimeToLiveExpirationPolicy(final long timeToLive, + final TimeUnit timeUnit) { + this(validateAndConvertToMillis(timeToLive, timeUnit)); + } + + /** + * Determine the expiration time for the given key-value entry. + * + * @param key the key for the entry (ignored). + * @param value the value for the entry (ignored). + * @return if {@link #timeToLiveMillis} ≥ 0, an expiration time of + * {@link #timeToLiveMillis} + + * {@link System#currentTimeMillis()} is returned. Otherwise, -1 + * is returned indicating the entry never expires. + */ + public long expirationTime(final K key, final V value) { + if (timeToLiveMillis >= 0L) { + // avoid numerical overflow + final long now = System.currentTimeMillis(); + if (now > Long.MAX_VALUE - timeToLiveMillis) { + // expiration would be greater than Long.MAX_VALUE + // never expire + return -1; + } + + // timeToLiveMillis in the future + return now + timeToLiveMillis; + } + + // never expire + return -1L; + } + } + + /** + * A policy to determine the expiration time for key-value entries. + * + * @param the key object type. + * @param the value object type + * @since 4.0 + * @version $Id: PassiveExpiringMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ + public static interface ExpirationPolicy + extends Serializable { + + /** + * Determine the expiration time for the given key-value entry. + * + * @param key the key for the entry. + * @param value the value for the entry. + * @return the expiration time value measured in milliseconds. A + * negative return value indicates the entry never expires. + */ + long expirationTime(K key, V value); + } + + /** Serialization version */ + private static final long serialVersionUID = 1L; + + /** + * First validate the input parameters. If the parameters are valid, convert + * the given time measured in the given units to the same time measured in + * milliseconds. + * + * @param timeToLive the constant amount of time an entry is available + * before it expires. A negative value results in entries that NEVER + * expire. A zero value results in entries that ALWAYS expire. + * @param timeUnit the unit of time for the timeToLive + * parameter, must not be null. + * @throws NullPointerException if the time unit is null. + */ + private static long validateAndConvertToMillis(final long timeToLive, + final TimeUnit timeUnit) { + if (timeUnit == null) { + throw new NullPointerException("Time unit must not be null"); + } + return TimeUnit.MILLISECONDS.convert(timeToLive, timeUnit); + } + + /** map used to manage expiration times for the actual map entries. */ + private final Map expirationMap = new HashMap(); + + /** the policy used to determine time-to-live values for map entries. */ + private final ExpirationPolicy expiringPolicy; + + /** + * Default constructor. Constructs a map decorator that results in entries + * NEVER expiring. + */ + public PassiveExpiringMap() { + this(-1L); + } + + /** + * Construct a map decorator using the given expiration policy to determine + * expiration times. + * + * @param expiringPolicy the policy used to determine expiration times of + * entries as they are added. + * @throws NullPointerException if expiringPolicy is null + */ + public PassiveExpiringMap(final ExpirationPolicy expiringPolicy) { + this(expiringPolicy, new HashMap()); + } + + /** + * Construct a map decorator that decorates the given map and uses the given + * expiration policy to determine expiration times. If there are any + * elements already in the map being decorated, they will NEVER expire + * unless they are replaced. + * + * @param expiringPolicy the policy used to determine expiration times of + * entries as they are added. + * @param map the map to decorate, must not be null. + * @throws NullPointerException if the map or expiringPolicy is null. + */ + public PassiveExpiringMap(final ExpirationPolicy expiringPolicy, + final Map map) { + super(map); + if (expiringPolicy == null) { + throw new NullPointerException("Policy must not be null."); + } + this.expiringPolicy = expiringPolicy; + } + + /** + * Construct a map decorator that decorates the given map using the given + * time-to-live value measured in milliseconds to create and use a + * {@link ConstantTimeToLiveExpirationPolicy} expiration policy. + * + * @param timeToLiveMillis the constant amount of time (in milliseconds) an + * entry is available before it expires. A negative value results in + * entries that NEVER expire. A zero value results in entries that + * ALWAYS expire. + */ + public PassiveExpiringMap(final long timeToLiveMillis) { + this(new ConstantTimeToLiveExpirationPolicy(timeToLiveMillis), + new HashMap()); + } + + /** + * Construct a map decorator using the given time-to-live value measured in + * milliseconds to create and use a + * {@link ConstantTimeToLiveExpirationPolicy} expiration policy. If there + * are any elements already in the map being decorated, they will NEVER + * expire unless they are replaced. + * + * @param timeToLiveMillis the constant amount of time (in milliseconds) an + * entry is available before it expires. A negative value results in + * entries that NEVER expire. A zero value results in entries that + * ALWAYS expire. + * @param map the map to decorate, must not be null. + * @throws NullPointerException if the map is null. + */ + public PassiveExpiringMap(final long timeToLiveMillis, final Map map) { + this(new ConstantTimeToLiveExpirationPolicy(timeToLiveMillis), + map); + } + + /** + * Construct a map decorator using the given time-to-live value measured in + * the given time units of measure to create and use a + * {@link ConstantTimeToLiveExpirationPolicy} expiration policy. + * + * @param timeToLive the constant amount of time an entry is available + * before it expires. A negative value results in entries that NEVER + * expire. A zero value results in entries that ALWAYS expire. + * @param timeUnit the unit of time for the timeToLive + * parameter, must not be null. + * @throws NullPointerException if the time unit is null. + */ + public PassiveExpiringMap(final long timeToLive, final TimeUnit timeUnit) { + this(validateAndConvertToMillis(timeToLive, timeUnit)); + } + + /** + * Construct a map decorator that decorates the given map using the given + * time-to-live value measured in the given time units of measure to create + * {@link ConstantTimeToLiveExpirationPolicy} expiration policy. This policy + * is used to determine expiration times. If there are any elements already + * in the map being decorated, they will NEVER expire unless they are + * replaced. + * + * @param timeToLive the constant amount of time an entry is available + * before it expires. A negative value results in entries that NEVER + * expire. A zero value results in entries that ALWAYS expire. + * @param timeUnit the unit of time for the timeToLive + * parameter, must not be null. + * @param map the map to decorate, must not be null. + * @throws NullPointerException if the map or time unit is null. + */ + public PassiveExpiringMap(final long timeToLive, final TimeUnit timeUnit, final Map map) { + this(validateAndConvertToMillis(timeToLive, timeUnit), map); + } + + /** + * Constructs a map decorator that decorates the given map and results in + * entries NEVER expiring. If there are any elements already in the map + * being decorated, they also will NEVER expire. + * + * @param map the map to decorate, must not be null. + * @throws NullPointerException if the map is null. + */ + public PassiveExpiringMap(final Map map) { + this(-1L, map); + } + + /** + * Normal {@link Map#clear()} behavior with the addition of clearing all + * expiration entries as well. + */ + @Override + public void clear() { + super.clear(); + expirationMap.clear(); + } + + /** + * All expired entries are removed from the map prior to determining the + * contains result. + * {@inheritDoc} + */ + @Override + public boolean containsKey(final Object key) { + removeIfExpired(key, now()); + return super.containsKey(key); + } + + /** + * All expired entries are removed from the map prior to determining the + * contains result. + * {@inheritDoc} + */ + @Override + public boolean containsValue(final Object value) { + removeAllExpired(now()); + return super.containsValue(value); + } + + /** + * All expired entries are removed from the map prior to returning the entry set. + * {@inheritDoc} + */ + @Override + public Set> entrySet() { + removeAllExpired(now()); + return super.entrySet(); + } + + /** + * All expired entries are removed from the map prior to returning the entry value. + * {@inheritDoc} + */ + @Override + public V get(final Object key) { + removeIfExpired(key, now()); + return super.get(key); + } + + /** + * All expired entries are removed from the map prior to determining if it is empty. + * {@inheritDoc} + */ + @Override + public boolean isEmpty() { + removeAllExpired(now()); + return super.isEmpty(); + } + + /** + * Determines if the given expiration time is less than now. + * + * @param now the time in milliseconds used to compare against the + * expiration time. + * @param expirationTimeObject the expiration time value retrieved from + * {@link #expirationMap}, can be null. + * @return true if expirationTimeObject is ≥ 0 + * and expirationTimeObject < now. + * false otherwise. + */ + private boolean isExpired(final long now, final Long expirationTimeObject) { + if (expirationTimeObject != null) { + final long expirationTime = expirationTimeObject.longValue(); + return expirationTime >= 0 && now >= expirationTime; + } + return false; + } + + /** + * All expired entries are removed from the map prior to returning the key set. + * {@inheritDoc} + */ + @Override + public Set keySet() { + removeAllExpired(now()); + return super.keySet(); + } + + /** + * The current time in milliseconds. + */ + private long now() { + return System.currentTimeMillis(); + } + + /** + * Add the given key-value pair to this map as well as recording the entry's expiration time based on + * the current time in milliseconds and this map's {@link #expiringPolicy}. + *

              + * {@inheritDoc} + */ + @Override + public V put(final K key, final V value) { + // record expiration time of new entry + final long expirationTime = expiringPolicy.expirationTime(key, value); + expirationMap.put(key, Long.valueOf(expirationTime)); + + return super.put(key, value); + } + + @Override + public void putAll(final Map mapToCopy) { + for (final Map.Entry entry : mapToCopy.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + /** + * Normal {@link Map#remove(Object)} behavior with the addition of removing + * any expiration entry as well. + * {@inheritDoc} + */ + @Override + public V remove(final Object key) { + expirationMap.remove(key); + return super.remove(key); + } + + /** + * Removes all entries in the map whose expiration time is less than + * now. The exceptions are entries with negative expiration + * times; those entries are never removed. + * + * @see #isExpired(long, Long) + */ + private void removeAllExpired(final long now) { + final Iterator> iter = expirationMap.entrySet().iterator(); + while (iter.hasNext()) { + final Map.Entry expirationEntry = iter.next(); + if (isExpired(now, expirationEntry.getValue())) { + // remove entry from collection + super.remove(expirationEntry.getKey()); + // remove entry from expiration map + iter.remove(); + } + } + } + + /** + * Removes the entry with the given key if the entry's expiration time is + * less than now. If the entry has a negative expiration time, + * the entry is never removed. + */ + private void removeIfExpired(final Object key, final long now) { + final Long expirationTimeObject = expirationMap.get(key); + if (isExpired(now, expirationTimeObject)) { + remove(key); + } + } + + /** + * All expired entries are removed from the map prior to returning the size. + * {@inheritDoc} + */ + @Override + public int size() { + removeAllExpired(now()); + return super.size(); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + */ + @SuppressWarnings("unchecked") + // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) + throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); // (1) + } + + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) + throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * All expired entries are removed from the map prior to returning the value collection. + * {@inheritDoc} + */ + @Override + public Collection values() { + removeAllExpired(now()); + return super.values(); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/PredicatedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/PredicatedMap.java new file mode 100644 index 000000000..1ec181814 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/PredicatedMap.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.collections4.Predicate; + +/** + * Decorates another Map to validate that additions + * match a specified predicate. + *

              + * This map exists to provide validation for the decorated map. + * It is normally created to decorate an empty map. + * If an object cannot be added to the map, an IllegalArgumentException is thrown. + *

              + * One usage would be to ensure that no null keys are added to the map. + *

              Map map = PredicatedSet.decorate(new HashMap(), NotNullPredicate.INSTANCE, null);
              + *

              + * Note that PredicatedMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: PredicatedMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PredicatedMap + extends AbstractInputCheckedMapDecorator + implements Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 7412622456128415156L; + + /** The key predicate to use */ + protected final Predicate keyPredicate; + + /** The value predicate to use */ + protected final Predicate valuePredicate; + + /** + * Factory method to create a predicated (validating) map. + *

              + * If there are any elements already in the list being decorated, they + * are validated. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param keyPredicate the predicate to validate the keys, null means no check + * @param valuePredicate the predicate to validate to values, null means no check + * @return a new predicated map + * @throws NullPointerException if the map is null + * @since 4.0 + */ + public static PredicatedMap predicatedMap(final Map map, + final Predicate keyPredicate, + final Predicate valuePredicate) { + return new PredicatedMap(map, keyPredicate, valuePredicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @param keyPredicate the predicate to validate the keys, null means no check + * @param valuePredicate the predicate to validate to values, null means no check + * @throws NullPointerException if the map is null + */ + protected PredicatedMap(final Map map, final Predicate keyPredicate, + final Predicate valuePredicate) { + super(map); + this.keyPredicate = keyPredicate; + this.valuePredicate = valuePredicate; + + final Iterator> it = map.entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry entry = it.next(); + validate(entry.getKey(), entry.getValue()); + } + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + * @since 3.1 + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since 3.1 + */ + @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); // (1) + } + + //----------------------------------------------------------------------- + /** + * Validates a key value pair. + * + * @param key the key to validate + * @param value the value to validate + * @throws IllegalArgumentException if invalid + */ + protected void validate(final K key, final V value) { + if (keyPredicate != null && keyPredicate.evaluate(key) == false) { + throw new IllegalArgumentException("Cannot add key - Predicate rejected it"); + } + if (valuePredicate != null && valuePredicate.evaluate(value) == false) { + throw new IllegalArgumentException("Cannot add value - Predicate rejected it"); + } + } + + /** + * Override to validate an object set into the map via setValue. + * + * @param value the value to validate + * @return the value itself + * @throws IllegalArgumentException if invalid + * @since 3.1 + */ + @Override + protected V checkSetValue(final V value) { + if (valuePredicate.evaluate(value) == false) { + throw new IllegalArgumentException("Cannot set value - Predicate rejected it"); + } + return value; + } + + /** + * Override to only return true when there is a value transformer. + * + * @return true if a value predicate is in use + * @since 3.1 + */ + @Override + protected boolean isSetValueChecking() { + return valuePredicate != null; + } + + //----------------------------------------------------------------------- + @Override + public V put(final K key, final V value) { + validate(key, value); + return map.put(key, value); + } + + @Override + public void putAll(final Map mapToCopy) { + for (final Map.Entry entry : mapToCopy.entrySet()) { + validate(entry.getKey(), entry.getValue()); + } + super.putAll(mapToCopy); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/PredicatedSortedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/PredicatedSortedMap.java new file mode 100644 index 000000000..96b225758 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/PredicatedSortedMap.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.util.Comparator; +import java.util.SortedMap; + +import org.apache.commons.collections4.Predicate; + +/** + * Decorates another SortedMap to validate that additions + * match a specified predicate. + *

              + * This map exists to provide validation for the decorated map. + * It is normally created to decorate an empty map. + * If an object cannot be added to the map, an IllegalArgumentException is thrown. + *

              + * One usage would be to ensure that no null keys are added to the map. + *

              + *   SortedMap map =
              + *     PredicatedSortedMap.predicatedSortedMap(new TreeMap(),
              + *                                             NotNullPredicate.notNullPredicate(),
              + *                                             null);
              + * 
              + *

              + * Note that PredicatedSortedMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedSortedMap}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: PredicatedSortedMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PredicatedSortedMap extends PredicatedMap implements SortedMap { + + /** Serialization version */ + private static final long serialVersionUID = 3359846175935304332L; + + /** + * Factory method to create a predicated (validating) sorted map. + *

              + * If there are any elements already in the list being decorated, they + * are validated. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param keyPredicate the predicate to validate the keys, null means no check + * @param valuePredicate the predicate to validate to values, null means no check + * @return a new predicated sorted map + * @throws NullPointerException if the map is null + * @since 4.0 + */ + public static PredicatedSortedMap predicatedSortedMap(final SortedMap map, + final Predicate keyPredicate, final Predicate valuePredicate) { + return new PredicatedSortedMap(map, keyPredicate, valuePredicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @param keyPredicate the predicate to validate the keys, null means no check + * @param valuePredicate the predicate to validate to values, null means no check + * @throws NullPointerException if the map is null + */ + protected PredicatedSortedMap(final SortedMap map, final Predicate keyPredicate, + final Predicate valuePredicate) { + super(map, keyPredicate, valuePredicate); + } + + //----------------------------------------------------------------------- + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + protected SortedMap getSortedMap() { + return (SortedMap) map; + } + + //----------------------------------------------------------------------- + public K firstKey() { + return getSortedMap().firstKey(); + } + + public K lastKey() { + return getSortedMap().lastKey(); + } + + public Comparator comparator() { + return getSortedMap().comparator(); + } + + public SortedMap subMap(final K fromKey, final K toKey) { + final SortedMap map = getSortedMap().subMap(fromKey, toKey); + return new PredicatedSortedMap(map, keyPredicate, valuePredicate); + } + + public SortedMap headMap(final K toKey) { + final SortedMap map = getSortedMap().headMap(toKey); + return new PredicatedSortedMap(map, keyPredicate, valuePredicate); + } + + public SortedMap tailMap(final K fromKey) { + final SortedMap map = getSortedMap().tailMap(fromKey); + return new PredicatedSortedMap(map, keyPredicate, valuePredicate); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/ReferenceIdentityMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/ReferenceIdentityMap.java new file mode 100644 index 000000000..3b894a097 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/ReferenceIdentityMap.java @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.Reference; + +/** + * A Map implementation that allows mappings to be + * removed by the garbage collector and matches keys and values based + * on == not equals(). + *

              + *

              + * When you construct a ReferenceIdentityMap, you can specify what kind + * of references are used to store the map's keys and values. + * If non-hard references are used, then the garbage collector can remove + * mappings if a key or value becomes unreachable, or if the JVM's memory is + * running low. For information on how the different reference types behave, + * see {@link Reference}. + *

              + * Different types of references can be specified for keys and values. + * The default constructor uses hard keys and soft values, providing a + * memory-sensitive cache. + *

              + * This map is similar to + * {@link org.apache.commons.collections4.map.ReferenceMap ReferenceMap}. + * It differs in that keys and values in this class are compared using ==. + *

              + * This map will violate the detail of various Map and map view contracts. + * As a general rule, don't compare this map to other maps. + *

              + * This {@link java.util.Map Map} implementation does not allow null elements. + * Attempting to add a null key or value to the map will raise a NullPointerException. + *

              + * This implementation is not synchronized. + * You can use {@link java.util.Collections#synchronizedMap} to + * provide synchronized access to a ReferenceIdentityMap. + * Remember that synchronization will not stop the garbage collector removing entries. + *

              + * All the available iterators can be reset back to the start by casting to + * ResettableIterator and calling reset(). + *

              + * Note that ReferenceIdentityMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + * + * @see java.lang.ref.Reference + * + * @since 3.0 (previously in main package v2.1) + * @version $Id: ReferenceIdentityMap.java 1477799 2013-04-30 19:56:11Z tn $ + */ +public class ReferenceIdentityMap extends AbstractReferenceMap implements Serializable { + + /** Serialization version */ + private static final long serialVersionUID = -1266190134568365852L; + + /** + * Constructs a new ReferenceIdentityMap that will + * use hard references to keys and soft references to values. + */ + public ReferenceIdentityMap() { + super(ReferenceStrength.HARD, ReferenceStrength.SOFT, DEFAULT_CAPACITY, + DEFAULT_LOAD_FACTOR, false); + } + + /** + * Constructs a new ReferenceIdentityMap that will + * use the specified types of references. + * + * @param keyType the type of reference to use for keys; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param valueType the type of reference to use for values; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + */ + public ReferenceIdentityMap(final ReferenceStrength keyType, final ReferenceStrength valueType) { + super(keyType, valueType, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, false); + } + + /** + * Constructs a new ReferenceIdentityMap that will + * use the specified types of references. + * + * @param keyType the type of reference to use for keys; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param valueType the type of reference to use for values; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param purgeValues should the value be automatically purged when the + * key is garbage collected + */ + public ReferenceIdentityMap(final ReferenceStrength keyType, final ReferenceStrength valueType, + final boolean purgeValues) { + super(keyType, valueType, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, purgeValues); + } + + /** + * Constructs a new ReferenceIdentityMap with the + * specified reference types, load factor and initial capacity. + * + * @param keyType the type of reference to use for keys; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param valueType the type of reference to use for values; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param capacity the initial capacity for the map + * @param loadFactor the load factor for the map + */ + public ReferenceIdentityMap(final ReferenceStrength keyType, final ReferenceStrength valueType, + final int capacity, final float loadFactor) { + super(keyType, valueType, capacity, loadFactor, false); + } + + /** + * Constructs a new ReferenceIdentityMap with the + * specified reference types, load factor and initial capacity. + * + * @param keyType the type of reference to use for keys; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param valueType the type of reference to use for values; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param capacity the initial capacity for the map + * @param loadFactor the load factor for the map + * @param purgeValues should the value be automatically purged when the + * key is garbage collected + */ + public ReferenceIdentityMap(final ReferenceStrength keyType, final ReferenceStrength valueType, + final int capacity, final float loadFactor, final boolean purgeValues) { + super(keyType, valueType, capacity, loadFactor, purgeValues); + } + + //----------------------------------------------------------------------- + /** + * Gets the hash code for the key specified. + *

              + * This implementation uses the identity hash code. + * + * @param key the key to get a hash code for + * @return the hash code + */ + @Override + protected int hash(final Object key) { + return System.identityHashCode(key); + } + + /** + * Gets the hash code for a MapEntry. + *

              + * This implementation uses the identity hash code. + * + * @param key the key to get a hash code for, may be null + * @param value the value to get a hash code for, may be null + * @return the hash code, as per the MapEntry specification + */ + @Override + protected int hashEntry(final Object key, final Object value) { + return System.identityHashCode(key) ^ + System.identityHashCode(value); + } + + /** + * Compares two keys for equals. + *

              + * This implementation converts the key from the entry to a real reference + * before comparison and uses ==. + * + * @param key1 the first key to compare passed in from outside + * @param key2 the second key extracted from the entry via entry.key + * @return true if equal by identity + */ + @Override + protected boolean isEqualKey(final Object key1, Object key2) { + key2 = isKeyType(ReferenceStrength.HARD) ? key2 : ((Reference) key2).get(); + return key1 == key2; + } + + /** + * Compares two values for equals. + *

              + * This implementation uses ==. + * + * @param value1 the first value to compare passed in from outside + * @param value2 the second value extracted from the entry via getValue() + * @return true if equal by identity + */ + @Override + protected boolean isEqualValue(final Object value1, final Object value2) { + return value1 == value2; + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + + /** + * Read the map in using a custom routine. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + doReadObject(in); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/ReferenceMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/ReferenceMap.java new file mode 100644 index 000000000..9b8de018a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/ReferenceMap.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * A Map implementation that allows mappings to be + * removed by the garbage collector. + *

              + * When you construct a ReferenceMap, you can specify what kind + * of references are used to store the map's keys and values. + * If non-hard references are used, then the garbage collector can remove + * mappings if a key or value becomes unreachable, or if the JVM's memory is + * running low. For information on how the different reference types behave, + * see {@link java.lang.ref.Reference Reference}. + *

              + * Different types of references can be specified for keys and values. + * The keys can be configured to be weak but the values hard, + * in which case this class will behave like a + * + * WeakHashMap. However, you can also specify hard keys and + * weak values, or any other combination. The default constructor uses + * hard keys and soft values, providing a memory-sensitive cache. + *

              + * This map is similar to + * {@link org.apache.commons.collections4.map.ReferenceIdentityMap ReferenceIdentityMap}. + * It differs in that keys and values in this class are compared using equals(). + *

              + * This {@link java.util.Map Map} implementation does not allow null elements. + * Attempting to add a null key or value to the map will raise a NullPointerException. + *

              + * This implementation is not synchronized. + * You can use {@link java.util.Collections#synchronizedMap} to + * provide synchronized access to a ReferenceMap. + * Remember that synchronization will not stop the garbage collector removing entries. + *

              + * All the available iterators can be reset back to the start by casting to + * ResettableIterator and calling reset(). + *

              + * Note that ReferenceMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

              + * NOTE: As from Commons Collections 3.1 this map extends AbstractReferenceMap + * (previously it extended AbstractMap). As a result, the implementation is now + * extensible and provides a MapIterator. + * + * @see java.lang.ref.Reference + * + * @since 3.0 (previously in main package v2.1) + * @version $Id: ReferenceMap.java 1477799 2013-04-30 19:56:11Z tn $ + */ +public class ReferenceMap extends AbstractReferenceMap implements Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 1555089888138299607L; + + /** + * Constructs a new ReferenceMap that will + * use hard references to keys and soft references to values. + */ + public ReferenceMap() { + super(ReferenceStrength.HARD, ReferenceStrength.SOFT, DEFAULT_CAPACITY, + DEFAULT_LOAD_FACTOR, false); + } + + /** + * Constructs a new ReferenceMap that will + * use the specified types of references. + * + * @param keyType the type of reference to use for keys; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param valueType the type of reference to use for values; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + */ + public ReferenceMap(final ReferenceStrength keyType, final ReferenceStrength valueType) { + super(keyType, valueType, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, false); + } + + /** + * Constructs a new ReferenceMap that will + * use the specified types of references. + * + * @param keyType the type of reference to use for keys; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param valueType the type of reference to use for values; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param purgeValues should the value be automatically purged when the + * key is garbage collected + */ + public ReferenceMap(final ReferenceStrength keyType, final ReferenceStrength valueType, final boolean purgeValues) { + super(keyType, valueType, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, purgeValues); + } + + /** + * Constructs a new ReferenceMap with the + * specified reference types, load factor and initial + * capacity. + * + * @param keyType the type of reference to use for keys; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param valueType the type of reference to use for values; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param capacity the initial capacity for the map + * @param loadFactor the load factor for the map + */ + public ReferenceMap(final ReferenceStrength keyType, final ReferenceStrength valueType, final int capacity, + final float loadFactor) { + super(keyType, valueType, capacity, loadFactor, false); + } + + /** + * Constructs a new ReferenceMap with the + * specified reference types, load factor and initial + * capacity. + * + * @param keyType the type of reference to use for keys; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param valueType the type of reference to use for values; + * must be {@link AbstractReferenceMap.ReferenceStrength#HARD HARD}, + * {@link AbstractReferenceMap.ReferenceStrength#SOFT SOFT}, + * {@link AbstractReferenceMap.ReferenceStrength#WEAK WEAK} + * @param capacity the initial capacity for the map + * @param loadFactor the load factor for the map + * @param purgeValues should the value be automatically purged when the + * key is garbage collected + */ + public ReferenceMap(final ReferenceStrength keyType, final ReferenceStrength valueType, final int capacity, + final float loadFactor, final boolean purgeValues) { + super(keyType, valueType, capacity, loadFactor, purgeValues); + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + + /** + * Read the map in using a custom routine. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + doReadObject(in); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/SingletonMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/SingletonMap.java new file mode 100644 index 000000000..95775065c --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/SingletonMap.java @@ -0,0 +1,576 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.commons.collections4.BoundedMap; +import org.apache.commons.collections4.KeyValue; +import org.apache.commons.collections4.OrderedMap; +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.ResettableIterator; +import org.apache.commons.collections4.iterators.SingletonIterator; +import org.apache.commons.collections4.keyvalue.TiedMapEntry; + +/** + * A Map implementation that holds a single item and is fixed size. + *

              + * The single key/value pair is specified at creation. + * The map is fixed size so any action that would change the size is disallowed. + * However, the put or setValue methods can change + * the value associated with the key. + *

              + * If trying to remove or clear the map, an UnsupportedOperationException is thrown. + * If trying to put a new mapping into the map, an IllegalArgumentException is thrown. + * The put method will only succeed if the key specified is the same as the + * singleton key. + *

              + * The key and value can be obtained by: + *

                + *
              • normal Map methods and views + *
              • the MapIterator, see {@link #mapIterator()} + *
              • the KeyValue interface (just cast - no object creation) + *
              + * + * @since 3.1 + * @version $Id: SingletonMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class SingletonMap + implements OrderedMap, BoundedMap, KeyValue, Serializable, Cloneable { + + /** Serialization version */ + private static final long serialVersionUID = -8931271118676803261L; + + /** Singleton key */ + private final K key; + /** Singleton value */ + private V value; + + /** + * Constructor that creates a map of null to null. + */ + public SingletonMap() { + super(); + this.key = null; + } + + /** + * Constructor specifying the key and value. + * + * @param key the key to use + * @param value the value to use + */ + public SingletonMap(final K key, final V value) { + super(); + this.key = key; + this.value = value; + } + + /** + * Constructor specifying the key and value as a KeyValue. + * + * @param keyValue the key value pair to use + */ + public SingletonMap(final KeyValue keyValue) { + super(); + this.key = keyValue.getKey(); + this.value = keyValue.getValue(); + } + + /** + * Constructor specifying the key and value as a MapEntry. + * + * @param mapEntry the mapEntry to use + */ + public SingletonMap(final Map.Entry mapEntry) { + super(); + this.key = mapEntry.getKey(); + this.value = mapEntry.getValue(); + } + + /** + * Constructor copying elements from another map. + * + * @param map the map to copy, must be size 1 + * @throws NullPointerException if the map is null + * @throws IllegalArgumentException if the size is not 1 + */ + public SingletonMap(final Map map) { + super(); + if (map.size() != 1) { + throw new IllegalArgumentException("The map size must be 1"); + } + final Map.Entry entry = map.entrySet().iterator().next(); + this.key = entry.getKey(); + this.value = entry.getValue(); + } + + // KeyValue + //----------------------------------------------------------------------- + /** + * Gets the key. + * + * @return the key + */ + public K getKey() { + return key; + } + + /** + * Gets the value. + * + * @return the value + */ + public V getValue() { + return value; + } + + /** + * Sets the value. + * + * @param value the new value to set + * @return the old value + */ + public V setValue(final V value) { + final V old = this.value; + this.value = value; + return old; + } + + // BoundedMap + //----------------------------------------------------------------------- + /** + * Is the map currently full, always true. + * + * @return true always + */ + public boolean isFull() { + return true; + } + + /** + * Gets the maximum size of the map, always 1. + * + * @return 1 always + */ + public int maxSize() { + return 1; + } + + // Map + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the key specified. + * + * @param key the key + * @return the mapped value, null if no match + */ + public V get(final Object key) { + if (isEqualKey(key)) { + return value; + } + return null; + } + + /** + * Gets the size of the map, always 1. + * + * @return the size of 1 + */ + public int size() { + return 1; + } + + /** + * Checks whether the map is currently empty, which it never is. + * + * @return false always + */ + public boolean isEmpty() { + return false; + } + + //----------------------------------------------------------------------- + /** + * Checks whether the map contains the specified key. + * + * @param key the key to search for + * @return true if the map contains the key + */ + public boolean containsKey(final Object key) { + return isEqualKey(key); + } + + /** + * Checks whether the map contains the specified value. + * + * @param value the value to search for + * @return true if the map contains the key + */ + public boolean containsValue(final Object value) { + return isEqualValue(value); + } + + //----------------------------------------------------------------------- + /** + * Puts a key-value mapping into this map where the key must match the existing key. + *

              + * An IllegalArgumentException is thrown if the key does not match as the map + * is fixed size. + * + * @param key the key to set, must be the key of the map + * @param value the value to set + * @return the value previously mapped to this key, null if none + * @throws IllegalArgumentException if the key does not match + */ + public V put(final K key, final V value) { + if (isEqualKey(key)) { + return setValue(value); + } + throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size singleton"); + } + + /** + * Puts the values from the specified map into this map. + *

              + * The map must be of size 0 or size 1. + * If it is size 1, the key must match the key of this map otherwise an + * IllegalArgumentException is thrown. + * + * @param map the map to add, must be size 0 or 1, and the key must match + * @throws NullPointerException if the map is null + * @throws IllegalArgumentException if the key does not match + */ + public void putAll(final Map map) { + switch (map.size()) { + case 0: + return; + + case 1: + final Map.Entry entry = map.entrySet().iterator().next(); + put(entry.getKey(), entry.getValue()); + return; + + default: + throw new IllegalArgumentException("The map size must be 0 or 1"); + } + } + + /** + * Unsupported operation. + * + * @param key the mapping to remove + * @return the value mapped to the removed key, null if key not in map + * @throws UnsupportedOperationException always + */ + public V remove(final Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Unsupported operation. + */ + public void clear() { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + /** + * Gets the entrySet view of the map. + * Changes made via setValue affect this map. + * To simply iterate through the entries, use {@link #mapIterator()}. + * + * @return the entrySet view + */ + public Set> entrySet() { + final Map.Entry entry = new TiedMapEntry(this, getKey()); + return Collections.singleton(entry); + } + + /** + * Gets the unmodifiable keySet view of the map. + * Changes made to the view affect this map. + * To simply iterate through the keys, use {@link #mapIterator()}. + * + * @return the keySet view + */ + public Set keySet() { + return Collections.singleton(key); + } + + /** + * Gets the unmodifiable values view of the map. + * Changes made to the view affect this map. + * To simply iterate through the values, use {@link #mapIterator()}. + * + * @return the values view + */ + public Collection values() { + return new SingletonValues(this); + } + + /** + * {@inheritDoc} + */ + public OrderedMapIterator mapIterator() { + return new SingletonMapIterator(this); + } + + /** + * Gets the first (and only) key in the map. + * + * @return the key + */ + public K firstKey() { + return getKey(); + } + + /** + * Gets the last (and only) key in the map. + * + * @return the key + */ + public K lastKey() { + return getKey(); + } + + /** + * Gets the next key after the key specified, always null. + * + * @param key the next key + * @return null always + */ + public K nextKey(final K key) { + return null; + } + + /** + * Gets the previous key before the key specified, always null. + * + * @param key the next key + * @return null always + */ + public K previousKey(final K key) { + return null; + } + + //----------------------------------------------------------------------- + /** + * Compares the specified key to the stored key. + * + * @param key the key to compare + * @return true if equal + */ + protected boolean isEqualKey(final Object key) { + return key == null ? getKey() == null : key.equals(getKey()); + } + + /** + * Compares the specified value to the stored value. + * + * @param value the value to compare + * @return true if equal + */ + protected boolean isEqualValue(final Object value) { + return value == null ? getValue() == null : value.equals(getValue()); + } + + //----------------------------------------------------------------------- + /** + * SingletonMapIterator. + */ + static class SingletonMapIterator implements OrderedMapIterator, ResettableIterator { + private final SingletonMap parent; + private boolean hasNext = true; + private boolean canGetSet = false; + + SingletonMapIterator(final SingletonMap parent) { + super(); + this.parent = parent; + } + + public boolean hasNext() { + return hasNext; + } + + public K next() { + if (hasNext == false) { + throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); + } + hasNext = false; + canGetSet = true; + return parent.getKey(); + } + + public boolean hasPrevious() { + return hasNext == false; + } + + public K previous() { + if (hasNext == true) { + throw new NoSuchElementException(AbstractHashedMap.NO_PREVIOUS_ENTRY); + } + hasNext = true; + return parent.getKey(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public K getKey() { + if (canGetSet == false) { + throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); + } + return parent.getKey(); + } + + public V getValue() { + if (canGetSet == false) { + throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); + } + return parent.getValue(); + } + + public V setValue(final V value) { + if (canGetSet == false) { + throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); + } + return parent.setValue(value); + } + + public void reset() { + hasNext = true; + } + + @Override + public String toString() { + if (hasNext) { + return "Iterator[]"; + } + return "Iterator[" + getKey() + "=" + getValue() + "]"; + } + } + + /** + * Values implementation for the SingletonMap. + * This class is needed as values is a view that must update as the map updates. + */ + static class SingletonValues extends AbstractSet implements Serializable { + private static final long serialVersionUID = -3689524741863047872L; + private final SingletonMap parent; + + SingletonValues(final SingletonMap parent) { + super(); + this.parent = parent; + } + + @Override + public int size() { + return 1; + } + @Override + public boolean isEmpty() { + return false; + } + @Override + public boolean contains(final Object object) { + return parent.containsValue(object); + } + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + @Override + public Iterator iterator() { + return new SingletonIterator(parent.getValue(), false); + } + } + + //----------------------------------------------------------------------- + /** + * Clones the map without cloning the key or value. + * + * @return a shallow clone + */ + @Override + @SuppressWarnings("unchecked") + public SingletonMap clone() { + try { + return (SingletonMap) super.clone(); + } catch (final CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + /** + * Compares this map with another. + * + * @param obj the object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map == false) { + return false; + } + final Map other = (Map) obj; + if (other.size() != 1) { + return false; + } + final Map.Entry entry = other.entrySet().iterator().next(); + return isEqualKey(entry.getKey()) && isEqualValue(entry.getValue()); + } + + /** + * Gets the standard Map hashCode. + * + * @return the hash code defined in the Map interface + */ + @Override + public int hashCode() { + return (getKey() == null ? 0 : getKey().hashCode()) ^ + (getValue() == null ? 0 : getValue().hashCode()); + } + + /** + * Gets the map as a String. + * + * @return a string version of the map + */ + @Override + public String toString() { + return new StringBuilder(128) + .append('{') + .append(getKey() == this ? "(this Map)" : getKey()) + .append('=') + .append(getValue() == this ? "(this Map)" : getValue()) + .append('}') + .toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/StaticBucketMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/StaticBucketMap.java new file mode 100644 index 000000000..1a8433e97 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/StaticBucketMap.java @@ -0,0 +1,718 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.commons.collections4.KeyValue; + +/** + * A StaticBucketMap is an efficient, thread-safe implementation of + * java.util.Map that performs well in in a highly + * thread-contentious environment. The map supports very efficient + * {@link #get(Object) get}, {@link #put(Object,Object) put}, + * {@link #remove(Object) remove} and {@link #containsKey(Object) containsKey} + * operations, assuming (approximate) uniform hashing and + * that the number of entries does not exceed the number of buckets. If the + * number of entries exceeds the number of buckets or if the hash codes of the + * objects are not uniformly distributed, these operations have a worst case + * scenario that is proportional to the number of elements in the map + * (O(n)).

              + * + * Each bucket in the hash table has its own monitor, so two threads can + * safely operate on the map at the same time, often without incurring any + * monitor contention. This means that you don't have to wrap instances + * of this class with {@link java.util.Collections#synchronizedMap(Map)}; + * instances are already thread-safe. Unfortunately, however, this means + * that this map implementation behaves in ways you may find disconcerting. + * Bulk operations, such as {@link #putAll(Map) putAll} or the + * {@link Collection#retainAll(Collection) retainAll} operation in collection + * views, are not atomic. If two threads are simultaneously + * executing + * + *

              + *   staticBucketMapInstance.putAll(map);
              + * 
              + * + * and + * + *
              + *   staticBucketMapInstance.entrySet().removeAll(map.entrySet());
              + * 
              + * + * then the results are generally random. Those two statement could cancel + * each other out, leaving staticBucketMapInstance essentially + * unchanged, or they could leave some random subset of map in + * staticBucketMapInstance.

              + * + * Also, much like an encyclopedia, the results of {@link #size()} and + * {@link #isEmpty()} are out-of-date as soon as they are produced.

              + * + * The iterators returned by the collection views of this class are not + * fail-fast. They will never raise a + * {@link java.util.ConcurrentModificationException}. Keys and values + * added to the map after the iterator is created do not necessarily appear + * during iteration. Similarly, the iterator does not necessarily fail to + * return keys and values that were removed after the iterator was created.

              + * + * Finally, unlike {@link java.util.HashMap}-style implementations, this + * class never rehashes the map. The number of buckets is fixed + * at construction time and never altered. Performance may degrade if + * you do not allocate enough buckets upfront.

              + * + * The {@link #atomic(Runnable)} method is provided to allow atomic iterations + * and bulk operations; however, overuse of {@link #atomic(Runnable) atomic} + * will basically result in a map that's slower than an ordinary synchronized + * {@link java.util.HashMap}. + * + * Use this class if you do not require reliable bulk operations and + * iterations, or if you can make your own guarantees about how bulk + * operations will affect the map.

              + * + * @since 3.0 (previously in main package v2.1) + * @version $Id: StaticBucketMap.java 1477799 2013-04-30 19:56:11Z tn $ + */ +public final class StaticBucketMap extends AbstractIterableMap { + + /** The default number of buckets to use */ + private static final int DEFAULT_BUCKETS = 255; + /** The array of buckets, where the actual data is held */ + private final Node[] buckets; + /** The matching array of locks */ + private final Lock[] locks; + + /** + * Initializes the map with the default number of buckets (255). + */ + public StaticBucketMap() { + this(DEFAULT_BUCKETS); + } + + /** + * Initializes the map with a specified number of buckets. The number + * of buckets is never below 17, and is always an odd number (StaticBucketMap + * ensures this). The number of buckets is inversely proportional to the + * chances for thread contention. The fewer buckets, the more chances for + * thread contention. The more buckets the fewer chances for thread + * contention. + * + * @param numBuckets the number of buckets for this map + */ + @SuppressWarnings("unchecked") + public StaticBucketMap(final int numBuckets) { + int size = Math.max(17, numBuckets); + + // Ensure that bucketSize is never a power of 2 (to ensure maximal distribution) + if (size % 2 == 0) { + size--; + } + + buckets = new Node[size]; + locks = new Lock[size]; + + for (int i = 0; i < size; i++) { + locks[i] = new Lock(); + } + } + + //----------------------------------------------------------------------- + /** + * Determine the exact hash entry for the key. The hash algorithm + * is rather simplistic, but it does the job: + * + *

              +     *   He = |Hk mod n|
              +     * 
              + * + *

              + * He is the entry's hashCode, Hk is the key's hashCode, and n is + * the number of buckets. + *

              + */ + private int getHash(final Object key) { + if (key == null) { + return 0; + } + int hash = key.hashCode(); + hash += ~(hash << 15); + hash ^= (hash >>> 10); + hash += (hash << 3); + hash ^= (hash >>> 6); + hash += ~(hash << 11); + hash ^= (hash >>> 16); + hash %= buckets.length; + return (hash < 0) ? hash * -1 : hash; + } + + /** + * Gets the current size of the map. + * The value is computed fresh each time the method is called. + * + * @return the current size + */ + public int size() { + int cnt = 0; + + for (int i = 0; i < buckets.length; i++) { + synchronized(locks[i]) { + cnt += locks[i].size; + } + } + return cnt; + } + + /** + * Checks if the size is currently zero. + * + * @return true if empty + */ + public boolean isEmpty() { + return (size() == 0); + } + + /** + * Gets the value associated with the key. + * + * @param key the key to retrieve + * @return the associated value + */ + public V get(final Object key) { + final int hash = getHash(key); + + synchronized (locks[hash]) { + Node n = buckets[hash]; + + while (n != null) { + if (n.key == key || (n.key != null && n.key.equals(key))) { + return n.value; + } + + n = n.next; + } + } + return null; + } + + /** + * Checks if the map contains the specified key. + * + * @param key the key to check + * @return true if found + */ + public boolean containsKey(final Object key) { + final int hash = getHash(key); + + synchronized (locks[hash]) { + Node n = buckets[hash]; + + while (n != null) { + if (n.key == key || (n.key != null && n.key.equals(key))) { + return true; + } + + n = n.next; + } + } + return false; + } + + /** + * Checks if the map contains the specified value. + * + * @param value the value to check + * @return true if found + */ + public boolean containsValue(final Object value) { + for (int i = 0; i < buckets.length; i++) { + synchronized (locks[i]) { + Node n = buckets[i]; + + while (n != null) { + if (n.value == value || (n.value != null && n.value.equals(value))) { + return true; + } + + n = n.next; + } + } + } + return false; + } + + //----------------------------------------------------------------------- + /** + * Puts a new key value mapping into the map. + * + * @param key the key to use + * @param value the value to use + * @return the previous mapping for the key + */ + public V put(final K key, final V value) { + final int hash = getHash(key); + + synchronized (locks[hash]) { + Node n = buckets[hash]; + + if (n == null) { + n = new Node(); + n.key = key; + n.value = value; + buckets[hash] = n; + locks[hash].size++; + return null; + } + + // Set n to the last node in the linked list. Check each key along the way + // If the key is found, then change the value of that node and return + // the old value. + for (Node next = n; next != null; next = next.next) { + n = next; + + if (n.key == key || (n.key != null && n.key.equals(key))) { + final V returnVal = n.value; + n.value = value; + return returnVal; + } + } + + // The key was not found in the current list of nodes, add it to the end + // in a new node. + final Node newNode = new Node(); + newNode.key = key; + newNode.value = value; + n.next = newNode; + locks[hash].size++; + } + return null; + } + + /** + * Removes the specified key from the map. + * + * @param key the key to remove + * @return the previous value at this key + */ + public V remove(final Object key) { + final int hash = getHash(key); + + synchronized (locks[hash]) { + Node n = buckets[hash]; + Node prev = null; + + while (n != null) { + if (n.key == key || (n.key != null && n.key.equals(key))) { + // Remove this node from the linked list of nodes. + if (null == prev) { + // This node was the head, set the next node to be the new head. + buckets[hash] = n.next; + } else { + // Set the next node of the previous node to be the node after this one. + prev.next = n.next; + } + locks[hash].size--; + return n.value; + } + + prev = n; + n = n.next; + } + } + return null; + } + + //----------------------------------------------------------------------- + /** + * Gets the key set. + * + * @return the key set + */ + public Set keySet() { + return new KeySet(); + } + + /** + * Gets the values. + * + * @return the values + */ + public Collection values() { + return new Values(); + } + + /** + * Gets the entry set. + * + * @return the entry set + */ + public Set> entrySet() { + return new EntrySet(); + } + + //----------------------------------------------------------------------- + /** + * Puts all the entries from the specified map into this map. + * This operation is not atomic and may have undesired effects. + * + * @param map the map of entries to add + */ + public void putAll(final Map map) { + for (final Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + /** + * Clears the map of all entries. + */ + public void clear() { + for (int i = 0; i < buckets.length; i++) { + final Lock lock = locks[i]; + synchronized (lock) { + buckets[i] = null; + lock.size = 0; + } + } + } + + /** + * Compares this map to another, as per the Map specification. + * + * @param obj the object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map == false) { + return false; + } + final Map other = (Map) obj; + return entrySet().equals(other.entrySet()); + } + + /** + * Gets the hash code, as per the Map specification. + * + * @return the hash code + */ + @Override + public int hashCode() { + int hashCode = 0; + + for (int i = 0; i < buckets.length; i++) { + synchronized (locks[i]) { + Node n = buckets[i]; + + while (n != null) { + hashCode += n.hashCode(); + n = n.next; + } + } + } + return hashCode; + } + + //----------------------------------------------------------------------- + /** + * The Map.Entry for the StaticBucketMap. + */ + private static final class Node implements Map.Entry, KeyValue { + protected K key; + protected V value; + protected Node next; + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + @Override + public int hashCode() { + return ((key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode())); + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map.Entry == false) { + return false; + } + + final Map.Entry e2 = (Map.Entry) obj; + return ( + (key == null ? e2.getKey() == null : key.equals(e2.getKey())) && + (value == null ? e2.getValue() == null : value.equals(e2.getValue()))); + } + + public V setValue(final V obj) { + final V retVal = value; + value = obj; + return retVal; + } + } + + /** + * The lock object, which also includes a count of the nodes in this lock. + */ + private final static class Lock { + public int size; + } + + //----------------------------------------------------------------------- + private class BaseIterator { + private final ArrayList> current = new ArrayList>(); + private int bucket; + private Map.Entry last; + + public boolean hasNext() { + if (current.size() > 0) { + return true; + } + while (bucket < buckets.length) { + synchronized (locks[bucket]) { + Node n = buckets[bucket]; + while (n != null) { + current.add(n); + n = n.next; + } + bucket++; + if (current.size() > 0) { + return true; + } + } + } + return false; + } + + protected Map.Entry nextEntry() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + last = current.remove(current.size() - 1); + return last; + } + + public void remove() { + if (last == null) { + throw new IllegalStateException(); + } + StaticBucketMap.this.remove(last.getKey()); + last = null; + } + } + + private class EntryIterator extends BaseIterator implements Iterator> { + + public Map.Entry next() { + return nextEntry(); + } + + } + + private class ValueIterator extends BaseIterator implements Iterator { + + public V next() { + return nextEntry().getValue(); + } + + } + + private class KeyIterator extends BaseIterator implements Iterator { + + public K next() { + return nextEntry().getKey(); + } + + } + + private class EntrySet extends AbstractSet> { + + @Override + public int size() { + return StaticBucketMap.this.size(); + } + + @Override + public void clear() { + StaticBucketMap.this.clear(); + } + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(final Object obj) { + final Map.Entry entry = (Map.Entry) obj; + final int hash = getHash(entry.getKey()); + synchronized (locks[hash]) { + for (Node n = buckets[hash]; n != null; n = n.next) { + if (n.equals(entry)) { + return true; + } + } + } + return false; + } + + @Override + public boolean remove(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + final Map.Entry entry = (Map.Entry) obj; + final int hash = getHash(entry.getKey()); + synchronized (locks[hash]) { + for (Node n = buckets[hash]; n != null; n = n.next) { + if (n.equals(entry)) { + StaticBucketMap.this.remove(n.getKey()); + return true; + } + } + } + return false; + } + + } + + private class KeySet extends AbstractSet { + + @Override + public int size() { + return StaticBucketMap.this.size(); + } + + @Override + public void clear() { + StaticBucketMap.this.clear(); + } + + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public boolean contains(final Object obj) { + return StaticBucketMap.this.containsKey(obj); + } + + @Override + public boolean remove(final Object obj) { + final int hash = getHash(obj); + synchronized (locks[hash]) { + for (Node n = buckets[hash]; n != null; n = n.next) { + final Object k = n.getKey(); + if ((k == obj) || ((k != null) && k.equals(obj))) { + StaticBucketMap.this.remove(k); + return true; + } + } + } + return false; + } + + } + + + private class Values extends AbstractCollection { + + @Override + public int size() { + return StaticBucketMap.this.size(); + } + + @Override + public void clear() { + StaticBucketMap.this.clear(); + } + + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + } + + /** + * Prevents any operations from occurring on this map while the + * given {@link Runnable} executes. This method can be used, for + * instance, to execute a bulk operation atomically: + * + *
              +     *    staticBucketMapInstance.atomic(new Runnable() {
              +     *        public void run() {
              +     *            staticBucketMapInstance.putAll(map);
              +     *        }
              +     *    });
              +     *  
              + * + * It can also be used if you need a reliable iterator: + * + *
              +     *    staticBucketMapInstance.atomic(new Runnable() {
              +     *        public void run() {
              +     *            Iterator iterator = staticBucketMapInstance.iterator();
              +     *            while (iterator.hasNext()) {
              +     *                foo(iterator.next();
              +     *            }
              +     *        }
              +     *    });
              +     *  
              + * + * Implementation note: This method requires a lot of time + * and a ton of stack space. Essentially a recursive algorithm is used + * to enter each bucket's monitor. If you have twenty thousand buckets + * in your map, then the recursive method will be invoked twenty thousand + * times. You have been warned. + * + * @param r the code to execute atomically + */ + public void atomic(final Runnable r) { + if (r == null) { + throw new NullPointerException(); + } + atomic(r, 0); + } + + private void atomic(final Runnable r, final int bucket) { + if (bucket >= buckets.length) { + r.run(); + return; + } + synchronized (locks[bucket]) { + atomic(r, bucket + 1); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/TransformedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/TransformedMap.java new file mode 100644 index 000000000..b016f4cca --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/TransformedMap.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Map; + +import org.apache.commons.collections4.Transformer; + +/** + * Decorates another Map to transform objects that are added. + *

              + * The Map put methods and Map.Entry setValue method are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must + * use the Integer form to remove objects. + *

              + * Note that TransformedMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

              + * This class is Serializable from Commons Collections 3.1. + *

              + * @see org.apache.commons.collections4.splitmap.TransformedSplitMap + * + * @since 3.0 + * @version $Id: TransformedMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TransformedMap + extends AbstractInputCheckedMapDecorator + implements Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 7023152376788900464L; + + /** The transformer to use for the key */ + protected final Transformer keyTransformer; + /** The transformer to use for the value */ + protected final Transformer valueTransformer; + + /** + * Factory method to create a transforming map. + *

              + * If there are any elements already in the map being decorated, they + * are NOT transformed. + * Contrast this with {@link #transformedMap(Map, Transformer, Transformer)}. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param keyTransformer the transformer to use for key conversion, null means no transformation + * @param valueTransformer the transformer to use for value conversion, null means no transformation + * @return a new transformed map + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static TransformedMap transformingMap(final Map map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + return new TransformedMap(map, keyTransformer, valueTransformer); + } + + /** + * Factory method to create a transforming map that will transform + * existing contents of the specified map. + *

              + * If there are any elements already in the map being decorated, they + * will be transformed by this method. + * Contrast this with {@link #transformingMap(Map, Transformer, Transformer)}. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param keyTransformer the transformer to use for key conversion, null means no transformation + * @param valueTransformer the transformer to use for value conversion, null means no transformation + * @return a new transformed map + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static TransformedMap transformedMap(final Map map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + final TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer); + if (map.size() > 0) { + final Map transformed = decorated.transformMap(map); + decorated.clear(); + decorated.decorated().putAll(transformed); // avoids double transformation + } + return decorated; + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the collection being decorated, they + * are NOT transformed. + * + * @param map the map to decorate, must not be null + * @param keyTransformer the transformer to use for key conversion, null means no conversion + * @param valueTransformer the transformer to use for value conversion, null means no conversion + * @throws NullPointerException if map is null + */ + protected TransformedMap(final Map map, final Transformer keyTransformer, + final Transformer valueTransformer) { + super(map); + this.keyTransformer = keyTransformer; + this.valueTransformer = valueTransformer; + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + * @since 3.1 + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since 3.1 + */ + @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); // (1) + } + + //----------------------------------------------------------------------- + /** + * Transforms a key. + *

              + * The transformer itself may throw an exception if necessary. + * + * @param object the object to transform + * @return the transformed object + */ + protected K transformKey(final K object) { + if (keyTransformer == null) { + return object; + } + return keyTransformer.transform(object); + } + + /** + * Transforms a value. + *

              + * The transformer itself may throw an exception if necessary. + * + * @param object the object to transform + * @return the transformed object + */ + protected V transformValue(final V object) { + if (valueTransformer == null) { + return object; + } + return valueTransformer.transform(object); + } + + /** + * Transforms a map. + *

              + * The transformer itself may throw an exception if necessary. + * + * @param map the map to transform + * @return the transformed object + */ + @SuppressWarnings("unchecked") + protected Map transformMap(final Map map) { + if (map.isEmpty()) { + return (Map) map; + } + final Map result = new LinkedMap(map.size()); + + for (final Map.Entry entry : map.entrySet()) { + result.put(transformKey(entry.getKey()), transformValue(entry.getValue())); + } + return result; + } + + /** + * Override to transform the value when using setValue. + * + * @param value the value to transform + * @return the transformed value + * @since 3.1 + */ + @Override + protected V checkSetValue(final V value) { + return valueTransformer.transform(value); + } + + /** + * Override to only return true when there is a value transformer. + * + * @return true if a value transformer is in use + * @since 3.1 + */ + @Override + protected boolean isSetValueChecking() { + return valueTransformer != null; + } + + //----------------------------------------------------------------------- + @Override + public V put(K key, V value) { + key = transformKey(key); + value = transformValue(value); + return decorated().put(key, value); + } + + @Override + public void putAll(Map mapToCopy) { + mapToCopy = transformMap(mapToCopy); + decorated().putAll(mapToCopy); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/TransformedSortedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/TransformedSortedMap.java new file mode 100644 index 000000000..727f57403 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/TransformedSortedMap.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; + +import org.apache.commons.collections4.Transformer; + +/** + * Decorates another SortedMap to transform objects that are added. + *

              + * The Map put methods and Map.Entry setValue method are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must + * use the Integer form to remove objects. + *

              + * Note that TransformedSortedMap is not synchronized and is not thread-safe. + * If you wish to use this map from multiple threads concurrently, you must use + * appropriate synchronization. The simplest approach is to wrap this map + * using {@link java.util.Collections#synchronizedSortedMap}. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: TransformedSortedMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TransformedSortedMap + extends TransformedMap + implements SortedMap { + + /** Serialization version */ + private static final long serialVersionUID = -8751771676410385778L; + + /** + * Factory method to create a transforming sorted map. + *

              + * If there are any elements already in the map being decorated, they are NOT transformed. + * Contrast this with {@link #transformedSortedMap(SortedMap, Transformer, Transformer)}. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param keyTransformer the predicate to validate the keys, null means no transformation + * @param valueTransformer the predicate to validate to values, null means no transformation + * @return a new transformed sorted map + * @throws NullPointerException if the map is null + * @since 4.0 + */ + public static TransformedSortedMap transformingSortedMap(final SortedMap map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + return new TransformedSortedMap(map, keyTransformer, valueTransformer); + } + + /** + * Factory method to create a transforming sorted map that will transform + * existing contents of the specified map. + *

              + * If there are any elements already in the map being decorated, they + * will be transformed by this method. + * Contrast this with {@link #transformingSortedMap(SortedMap, Transformer, Transformer)}. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @param keyTransformer the transformer to use for key conversion, null means no transformation + * @param valueTransformer the transformer to use for value conversion, null means no transformation + * @return a new transformed sorted map + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static TransformedSortedMap transformedSortedMap(final SortedMap map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + + final TransformedSortedMap decorated = + new TransformedSortedMap(map, keyTransformer, valueTransformer); + if (map.size() > 0) { + final Map transformed = decorated.transformMap(map); + decorated.clear(); + decorated.decorated().putAll(transformed); // avoids double transformation + } + return decorated; + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the collection being decorated, they + * are NOT transformed.

              + * + * @param map the map to decorate, must not be null + * @param keyTransformer the predicate to validate the keys, null means no transformation + * @param valueTransformer the predicate to validate to values, null means no transformation + * @throws NullPointerException if the map is null + */ + protected TransformedSortedMap(final SortedMap map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + super(map, keyTransformer, valueTransformer); + } + + //----------------------------------------------------------------------- + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + protected SortedMap getSortedMap() { + return (SortedMap) map; + } + + //----------------------------------------------------------------------- + public K firstKey() { + return getSortedMap().firstKey(); + } + + public K lastKey() { + return getSortedMap().lastKey(); + } + + public Comparator comparator() { + return getSortedMap().comparator(); + } + + public SortedMap subMap(final K fromKey, final K toKey) { + final SortedMap map = getSortedMap().subMap(fromKey, toKey); + return new TransformedSortedMap(map, keyTransformer, valueTransformer); + } + + public SortedMap headMap(final K toKey) { + final SortedMap map = getSortedMap().headMap(toKey); + return new TransformedSortedMap(map, keyTransformer, valueTransformer); + } + + public SortedMap tailMap(final K fromKey) { + final SortedMap map = getSortedMap().tailMap(fromKey); + return new TransformedSortedMap(map, keyTransformer, valueTransformer); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableEntrySet.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableEntrySet.java new file mode 100644 index 000000000..62d2706bf --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableEntrySet.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; +import org.apache.commons.collections4.keyvalue.AbstractMapEntryDecorator; +import org.apache.commons.collections4.set.AbstractSetDecorator; + +/** + * Decorates a map entry Set to ensure it can't be altered. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableEntrySet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableEntrySet + extends AbstractSetDecorator> implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = 1678353579659253473L; + + /** + * Factory method to create an unmodifiable set of Map Entry objects. + * + * @param the key type + * @param the value type + * @param set the set to decorate, must not be null + * @return a new unmodifiable entry set + * @throws NullPointerException if set is null + * @since 4.0 + */ + public static Set> unmodifiableEntrySet(final Set> set) { + if (set instanceof Unmodifiable) { + return set; + } + return new UnmodifiableEntrySet(set); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param set the set to decorate, must not be null + * @throws NullPointerException if set is null + */ + private UnmodifiableEntrySet(final Set> set) { + super(set); + } + + //----------------------------------------------------------------------- + @Override + public boolean add(final Map.Entry object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection> coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + @Override + public Iterator> iterator() { + return new UnmodifiableEntrySetIterator(decorated().iterator()); + } + + @Override + @SuppressWarnings("unchecked") + public Object[] toArray() { + final Object[] array = decorated().toArray(); + for (int i = 0; i < array.length; i++) { + array[i] = new UnmodifiableEntry((Map.Entry) array[i]); + } + return array; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(final T[] array) { + Object[] result = array; + if (array.length > 0) { + // we must create a new array to handle multi-threaded situations + // where another thread could access data before we decorate it + result = (Object[]) Array.newInstance(array.getClass().getComponentType(), 0); + } + result = decorated().toArray(result); + for (int i = 0; i < result.length; i++) { + result[i] = new UnmodifiableEntry((Map.Entry) result[i]); + } + + // check to see if result should be returned straight + if (result.length > array.length) { + return (T[]) result; + } + + // copy back into input array to fulfill the method contract + System.arraycopy(result, 0, array, 0, result.length); + if (array.length > result.length) { + array[result.length] = null; + } + return array; + } + + //----------------------------------------------------------------------- + /** + * Implementation of an entry set iterator. + */ + private class UnmodifiableEntrySetIterator extends AbstractIteratorDecorator> { + + protected UnmodifiableEntrySetIterator(final Iterator> iterator) { + super(iterator); + } + + @Override + public Map.Entry next() { + return new UnmodifiableEntry(getIterator().next()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + //----------------------------------------------------------------------- + /** + * Implementation of a map entry that is unmodifiable. + */ + private class UnmodifiableEntry extends AbstractMapEntryDecorator { + + protected UnmodifiableEntry(final Map.Entry entry) { + super(entry); + } + + @Override + public V setValue(final V obj) { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableMap.java new file mode 100644 index 000000000..47c72f7d5 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableMap.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.IterableMap; +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.collection.UnmodifiableCollection; +import org.apache.commons.collections4.iterators.EntrySetMapIterator; +import org.apache.commons.collections4.iterators.UnmodifiableMapIterator; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another Map to ensure it can't be altered. + *

              + * This class is Serializable from Commons Collections 3.1. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableMap + extends AbstractMapDecorator + implements Unmodifiable, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 2737023427269031941L; + + /** + * Factory method to create an unmodifiable map. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @return a new unmodifiable map + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static Map unmodifiableMap(final Map map) { + if (map instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final Map tmpMap = (Map) map; + return tmpMap; + } + return new UnmodifiableMap(map); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableMap(final Map map) { + super((Map) map); + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + * @since 3.1 + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since 3.1 + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); + } + + //----------------------------------------------------------------------- + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public V put(final K key, final V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(final Map mapToCopy) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public MapIterator mapIterator() { + if (map instanceof IterableMap) { + final MapIterator it = ((IterableMap) map).mapIterator(); + return UnmodifiableMapIterator.unmodifiableMapIterator(it); + } + final MapIterator it = new EntrySetMapIterator(map); + return UnmodifiableMapIterator.unmodifiableMapIterator(it); + } + + @Override + public Set> entrySet() { + final Set> set = super.entrySet(); + return UnmodifiableEntrySet.unmodifiableEntrySet(set); + } + + @Override + public Set keySet() { + final Set set = super.keySet(); + return UnmodifiableSet.unmodifiableSet(set); + } + + @Override + public Collection values() { + final Collection coll = super.values(); + return UnmodifiableCollection.unmodifiableCollection(coll); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableOrderedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableOrderedMap.java new file mode 100644 index 000000000..2a29c135c --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableOrderedMap.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.OrderedMap; +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.collection.UnmodifiableCollection; +import org.apache.commons.collections4.iterators.UnmodifiableOrderedMapIterator; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another OrderedMap to ensure it can't be altered. + *

              + * This class is Serializable from Commons Collections 3.1. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableOrderedMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableOrderedMap extends AbstractOrderedMapDecorator implements + Unmodifiable, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 8136428161720526266L; + + /** + * Factory method to create an unmodifiable sorted map. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @return a new ordered map + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static OrderedMap unmodifiableOrderedMap(final OrderedMap map) { + if (map instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final OrderedMap tmpMap = (OrderedMap) map; + return tmpMap; + } + return new UnmodifiableOrderedMap(map); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableOrderedMap(final OrderedMap map) { + super((OrderedMap) map); + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + * @since 3.1 + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since 3.1 + */ + @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); // (1) + } + + //----------------------------------------------------------------------- + @Override + public OrderedMapIterator mapIterator() { + final OrderedMapIterator it = decorated().mapIterator(); + return UnmodifiableOrderedMapIterator.unmodifiableOrderedMapIterator(it); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public V put(final K key, final V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(final Map mapToCopy) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + final Set> set = super.entrySet(); + return UnmodifiableEntrySet.unmodifiableEntrySet(set); + } + + @Override + public Set keySet() { + final Set set = super.keySet(); + return UnmodifiableSet.unmodifiableSet(set); + } + + @Override + public Collection values() { + final Collection coll = super.values(); + return UnmodifiableCollection.unmodifiableCollection(coll); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableSortedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableSortedMap.java new file mode 100644 index 000000000..62cb270c1 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/UnmodifiableSortedMap.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.map; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; + +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.collection.UnmodifiableCollection; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another SortedMap to ensure it can't be altered. + *

              + * This class is Serializable from Commons Collections 3.1. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableSortedMap.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableSortedMap + extends AbstractSortedMapDecorator + implements Unmodifiable, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 5805344239827376360L; + + /** + * Factory method to create an unmodifiable sorted map. + * + * @param the key type + * @param the value type + * @param map the map to decorate, must not be null + * @return a new unmodifiable sorted map + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static SortedMap unmodifiableSortedMap(final SortedMap map) { + if (map instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final SortedMap tmpMap = (SortedMap) map; + return tmpMap; + } + return new UnmodifiableSortedMap(map); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableSortedMap(final SortedMap map) { + super((SortedMap) map); + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + * @since 3.1 + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(map); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since 3.1 + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); + } + + //----------------------------------------------------------------------- + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public V put(final K key, final V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(final Map mapToCopy) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + return UnmodifiableEntrySet.unmodifiableEntrySet(super.entrySet()); + } + + @Override + public Set keySet() { + return UnmodifiableSet.unmodifiableSet(super.keySet()); + } + + @Override + public Collection values() { + return UnmodifiableCollection.unmodifiableCollection(super.values()); + } + + //----------------------------------------------------------------------- + @Override + public K firstKey() { + return decorated().firstKey(); + } + + @Override + public K lastKey() { + return decorated().lastKey(); + } + + @Override + public Comparator comparator() { + return decorated().comparator(); + } + + @Override + public SortedMap subMap(final K fromKey, final K toKey) { + return new UnmodifiableSortedMap(decorated().subMap(fromKey, toKey)); + } + + @Override + public SortedMap headMap(final K toKey) { + return new UnmodifiableSortedMap(decorated().headMap(toKey)); + } + + @Override + public SortedMap tailMap(final K fromKey) { + return new UnmodifiableSortedMap(decorated().tailMap(fromKey)); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/map/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/map/package-info.java new file mode 100644 index 000000000..f9670a026 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/map/package-info.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the {@link java.util.Map Map}, + * {@link org.apache.commons.collections4.IterableMap IterableMap}, + * {@link org.apache.commons.collections4.OrderedMap OrderedMap} and + * {@link java.util.SortedMap SortedMap} interfaces. + * A Map provides a lookup from a key to a value. + * A number of implementations also support the new MapIterator interface that enables + * simple iteration of map keys and values. + *

              + * The following implementations are provided: + *

                + *
              • CaseInsensitiveMap - map that compares keys in a case insensitive way + *
              • CompositeMap - map that combines multiple maps into a single view + *
              • HashedMap - general purpose HashMap replacement supporting MapIterator + *
              • Flat3Map - designed for good performance at size 3 or less + *
              • LinkedMap - a hash map that maintains insertion order, supporting OrderedMapIterator + *
              • LRUMap - a hash map that maintains a maximum size by removing the least recently used entries + *
              • MultiKeyMap - map that provides special methods for using more than one key to access the value + *
              • ReferenceMap - allows the garbage collector to collect keys and values using equals() for comparison + *
              • ReferenceIdentityMap - allows the garbage collector to collect keys and values using == for comparison + *
              • SingletonMap - a fully featured map to hold one key-value pair + *
              • StaticBucketMap - internally synchronized and designed for thread-contentious environments + *
              + *

              + * The following decorators are provided: + *

                + *
              • Unmodifiable - ensures the collection cannot be altered + *
              • Predicated - ensures that only elements that are valid according to a predicate can be added + *
              • Transformed - transforms each element added + *
              • FixedSize - ensures that the size of the map cannot change + *
              • Defaulted - provides default values for non-existing keys + *
              • Lazy - creates objects in the map on demand + *
              • ListOrdered - ensures that insertion order is retained + *
              + * + * @version $Id: package-info.java 1469004 2013-04-17 17:37:03Z tn $ + */ +package org.apache.commons.collections4.map; + diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractListValuedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractListValuedMap.java new file mode 100644 index 000000000..9ac8bb824 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractListValuedMap.java @@ -0,0 +1,289 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multimap; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.collections4.ListValuedMap; + +/** + * Abstract implementation of the {@link ListValuedMap} interface to simplify + * the creation of subclass implementations. + *

              + * Subclasses specify a Map implementation to use as the internal storage and + * the List implementation to use as values. + * + * @since 4.1 + * @version $Id: AbstractListValuedMap.java 1715695 2015-11-22 21:11:49Z tn $ + */ +public abstract class AbstractListValuedMap extends AbstractMultiValuedMap + implements ListValuedMap { + + /** + * Constructor needed for subclass serialisation. + */ + protected AbstractListValuedMap() { + super(); + } + + /** + * A constructor that wraps, not copies + * + * @param map the map to wrap, must not be null + * @throws NullPointerException if the map is null + */ + protected AbstractListValuedMap(final Map> map) { + super(map); + } + + // ----------------------------------------------------------------------- + @Override + @SuppressWarnings("unchecked") + protected Map> getMap() { + return (Map>) super.getMap(); + } + + /** + * Creates a new value collection using the provided factory. + * @return a new list + */ + @Override + protected abstract List createCollection(); + + // ----------------------------------------------------------------------- + /** + * Gets the list of values associated with the specified key. This would + * return an empty list in case the mapping is not present + * + * @param key the key to retrieve + * @return the {@code List} of values, will return an empty {@link List} for no mapping + */ + @Override + public List get(final K key) { + return wrappedCollection(key); + } + + @Override + List wrappedCollection(final K key) { + return new WrappedList(key); + } + + /** + * Removes all values associated with the specified key. + *

              + * A subsequent get(Object) would return an empty list. + * + * @param key the key to remove values from + * @return the List of values removed, will return an empty, + * unmodifiable list for no mapping found. + */ + @Override + public List remove(Object key) { + return ListUtils.emptyIfNull(getMap().remove(key)); + } + + // ----------------------------------------------------------------------- + /** + * Wrapped list to handle add and remove on the list returned by get(object) + */ + private class WrappedList extends WrappedCollection implements List { + + public WrappedList(final K key) { + super(key); + } + + @Override + protected List getMapping() { + return getMap().get(key); + } + + @Override + public void add(int index, V value) { + List list = getMapping(); + if (list == null) { + list = createCollection(); + getMap().put(key, list); + } + list.add(index, value); + } + + @Override + public boolean addAll(int index, Collection c) { + List list = getMapping(); + if (list == null) { + list = createCollection(); + boolean changed = list.addAll(index, c); + if (changed) { + getMap().put(key, list); + } + return changed; + } + return list.addAll(index, c); + } + + @Override + public V get(int index) { + final List list = ListUtils.emptyIfNull(getMapping()); + return list.get(index); + } + + @Override + public int indexOf(Object o) { + final List list = ListUtils.emptyIfNull(getMapping()); + return list.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + final List list = ListUtils.emptyIfNull(getMapping()); + return list.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return new ValuesListIterator(key); + } + + @Override + public ListIterator listIterator(int index) { + return new ValuesListIterator(key, index); + } + + @Override + public V remove(int index) { + final List list = ListUtils.emptyIfNull(getMapping()); + V value = list.remove(index); + if (list.isEmpty()) { + AbstractListValuedMap.this.remove(key); + } + return value; + } + + @Override + public V set(int index, V value) { + final List list = ListUtils.emptyIfNull(getMapping()); + return list.set(index, value); + } + + @Override + public List subList(int fromIndex, int toIndex) { + final List list = ListUtils.emptyIfNull(getMapping()); + return list.subList(fromIndex, toIndex); + } + + @Override + public boolean equals(Object other) { + final List list = getMapping(); + if (list == null) { + return Collections.emptyList().equals(other); + } + if (!(other instanceof List)) { + return false; + } + List otherList = (List) other; + return ListUtils.isEqualList(list, otherList); + } + + @Override + public int hashCode() { + final List list = getMapping(); + return ListUtils.hashCodeForList(list); + } + + } + + /** Values ListIterator */ + private class ValuesListIterator implements ListIterator { + + private final K key; + private List values; + private ListIterator iterator; + + public ValuesListIterator(final K key) { + this.key = key; + this.values = ListUtils.emptyIfNull(getMap().get(key)); + this.iterator = values.listIterator(); + } + + public ValuesListIterator(final K key, int index) { + this.key = key; + this.values = ListUtils.emptyIfNull(getMap().get(key)); + this.iterator = values.listIterator(index); + } + + @Override + public void add(V value) { + if (getMap().get(key) == null) { + List list = createCollection(); + getMap().put(key, list); + this.values = list; + this.iterator = list.listIterator(); + } + this.iterator.add(value); + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public boolean hasPrevious() { + return iterator.hasPrevious(); + } + + @Override + public V next() { + return iterator.next(); + } + + @Override + public int nextIndex() { + return iterator.nextIndex(); + } + + @Override + public V previous() { + return iterator.previous(); + } + + @Override + public int previousIndex() { + return iterator.previousIndex(); + } + + @Override + public void remove() { + iterator.remove(); + if (values.isEmpty()) { + getMap().remove(key); + } + } + + @Override + public void set(V value) { + iterator.set(value); + } + + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java new file mode 100644 index 000000000..ac35c51cd --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java @@ -0,0 +1,944 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multimap; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.MultiSet; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; +import org.apache.commons.collections4.iterators.EmptyMapIterator; +import org.apache.commons.collections4.iterators.IteratorChain; +import org.apache.commons.collections4.iterators.LazyIteratorChain; +import org.apache.commons.collections4.iterators.TransformIterator; +import org.apache.commons.collections4.keyvalue.AbstractMapEntry; +import org.apache.commons.collections4.keyvalue.UnmodifiableMapEntry; +import org.apache.commons.collections4.multiset.AbstractMultiSet; +import org.apache.commons.collections4.multiset.UnmodifiableMultiSet; + +/** + * Abstract implementation of the {@link MultiValuedMap} interface to simplify + * the creation of subclass implementations. + *

              + * Subclasses specify a Map implementation to use as the internal storage. + * + * @since 4.1 + * @version $Id: AbstractMultiValuedMap.java 1715697 2015-11-22 21:20:04Z tn $ + */ +public abstract class AbstractMultiValuedMap implements MultiValuedMap { + + /** The values view */ + private transient Collection valuesView; + + /** The EntryValues view */ + private transient EntryValues entryValuesView; + + /** The KeyMultiSet view */ + private transient MultiSet keysMultiSetView; + + /** The AsMap view */ + private transient AsMap asMapView; + + /** The map used to store the data */ + private transient Map> map; + + /** + * Constructor needed for subclass serialisation. + */ + protected AbstractMultiValuedMap() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param map the map to wrap, must not be null + * @throws NullPointerException if the map is null + */ + @SuppressWarnings("unchecked") + protected AbstractMultiValuedMap(final Map> map) { + if (map == null) { + throw new NullPointerException("Map must not be null."); + } + this.map = (Map>) map; + } + + // ----------------------------------------------------------------------- + /** + * Gets the map being wrapped. + * + * @return the wrapped map + */ + protected Map> getMap() { + return map; + } + + /** + * Sets the map being wrapped. + *

              + * NOTE: this method should only be used during deserialization + * + * @param map the map to wrap + */ + @SuppressWarnings("unchecked") + protected void setMap(Map> map) { + this.map = (Map>) map; + } + + protected abstract Collection createCollection(); + + // ----------------------------------------------------------------------- + @Override + public boolean containsKey(Object key) { + return getMap().containsKey(key); + } + + @Override + public boolean containsValue(final Object value) { + return values().contains(value); + } + + @Override + public boolean containsMapping(Object key, Object value) { + Collection coll = getMap().get(key); + return coll != null && coll.contains(value); + } + + @Override + public Collection> entries() { + return entryValuesView != null ? entryValuesView : (entryValuesView = new EntryValues()); + } + + /** + * Gets the collection of values associated with the specified key. This + * would return an empty collection in case the mapping is not present + * + * @param key the key to retrieve + * @return the {@code Collection} of values, will return an empty {@code Collection} for no mapping + */ + @Override + public Collection get(final K key) { + return wrappedCollection(key); + } + + Collection wrappedCollection(final K key) { + return new WrappedCollection(key); + } + + /** + * Removes all values associated with the specified key. + *

              + * A subsequent get(Object) would return an empty collection. + * + * @param key the key to remove values from + * @return the Collection of values removed, will return an + * empty, unmodifiable collection for no mapping found + */ + @Override + public Collection remove(Object key) { + return CollectionUtils.emptyIfNull(getMap().remove(key)); + } + + /** + * Removes a specific key/value mapping from the multi-valued map. + *

              + * The value is removed from the collection mapped to the specified key. + * Other values attached to that key are unaffected. + *

              + * If the last value for a key is removed, an empty collection would be + * returned from a subsequent {@link #get(Object)}. + * + * @param key the key to remove from + * @param value the value to remove + * @return true if the mapping was removed, false otherwise + */ + @Override + public boolean removeMapping(final Object key, final Object value) { + final Collection coll = getMap().get(key); + if (coll == null) { + return false; + } + boolean changed = coll.remove(value); + if (coll.isEmpty()) { + getMap().remove(key); + } + return changed; + } + + @Override + public boolean isEmpty() { + return getMap().isEmpty(); + } + + @Override + public Set keySet() { + return getMap().keySet(); + } + + /** + * {@inheritDoc} + *

              + * This implementation does not cache the total size + * of the multi-valued map, but rather calculates it by iterating + * over the entries of the underlying map. + */ + @Override + public int size() { + // the total size should be cached to improve performance + // but this requires that all modifications of the multimap + // (including the wrapped collections and entry/value + // collections) are tracked. + int size = 0; + for (final Collection col : getMap().values()) { + size += col.size(); + } + return size; + } + + /** + * Gets a collection containing all the values in the map. + *

              + * Returns a collection containing all the values from all keys. + * + * @return a collection view of the values contained in this map + */ + @Override + public Collection values() { + final Collection vs = valuesView; + return vs != null ? vs : (valuesView = new Values()); + } + + @Override + public void clear() { + getMap().clear(); + } + + /** + * Adds the value to the collection associated with the specified key. + *

              + * Unlike a normal Map the previous value is not replaced. + * Instead the new value is added to the collection stored against the key. + * + * @param key the key to store against + * @param value the value to add to the collection at the key + * @return the value added if the map changed and null if the map did not change + */ + @Override + public boolean put(final K key, final V value) { + Collection coll = getMap().get(key); + if (coll == null) { + coll = createCollection(); + if (coll.add(value)) { + map.put(key, coll); + return true; + } else { + return false; + } + } else { + return coll.add(value); + } + } + + /** + * Copies all of the mappings from the specified map to this map. The effect + * of this call is equivalent to that of calling {@link #put(Object,Object) + * put(k, v)} on this map once for each mapping from key {@code k} to value + * {@code v} in the specified map. The behavior of this operation is + * undefined if the specified map is modified while the operation is in + * progress. + * + * @param map mappings to be stored in this map, may not be null + * @return true if the map changed as a result of this operation + * @throws NullPointerException if map is null + */ + @Override + public boolean putAll(final Map map) { + if (map == null) { + throw new NullPointerException("Map must not be null."); + } + boolean changed = false; + for (Map.Entry entry : map.entrySet()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + /** + * Copies all of the mappings from the specified MultiValuedMap to this map. + * The effect of this call is equivalent to that of calling + * {@link #put(Object,Object) put(k, v)} on this map once for each mapping + * from key {@code k} to value {@code v} in the specified map. The + * behavior of this operation is undefined if the specified map is modified + * while the operation is in progress. + * + * @param map mappings to be stored in this map, may not be null + * @return true if the map changed as a result of this operation + * @throws NullPointerException if map is null + */ + @Override + public boolean putAll(final MultiValuedMap map) { + if (map == null) { + throw new NullPointerException("Map must not be null."); + } + boolean changed = false; + for (Map.Entry entry : map.entries()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + /** + * Returns a {@link MultiSet} view of the key mapping contained in this map. + *

              + * Returns a MultiSet of keys with its values count as the count of the MultiSet. + * This multiset is backed by the map, so any changes in the map is reflected here. + * Any method which modifies this multiset like {@code add}, {@code remove}, + * {@link Iterator#remove()} etc throws {@code UnsupportedOperationException}. + * + * @return a bag view of the key mapping contained in this map + */ + @Override + public MultiSet keys() { + if (keysMultiSetView == null) { + keysMultiSetView = UnmodifiableMultiSet.unmodifiableMultiSet(new KeysMultiSet()); + } + return keysMultiSetView; + } + + @Override + public Map> asMap() { + return asMapView != null ? asMapView : (asMapView = new AsMap(map)); + } + + /** + * Adds Iterable values to the collection associated with the specified key. + * + * @param key the key to store against + * @param values the values to add to the collection at the key, may not be null + * @return true if this map changed + * @throws NullPointerException if values is null + */ + @Override + public boolean putAll(final K key, final Iterable values) { + if (values == null) { + throw new NullPointerException("Values must not be null."); + } + + if (values instanceof Collection) { + Collection valueCollection = (Collection) values; + return !valueCollection.isEmpty() && get(key).addAll(valueCollection); + } else { + Iterator it = values.iterator(); + return it.hasNext() && CollectionUtils.addAll(get(key), it); + } + } + + @Override + public MapIterator mapIterator() { + if (size() == 0) { + return EmptyMapIterator.emptyMapIterator(); + } + return new MultiValuedMapIterator(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof MultiValuedMap) { + return asMap().equals(((MultiValuedMap) obj).asMap()); + } + return false; + } + + @Override + public int hashCode() { + return getMap().hashCode(); + } + + @Override + public String toString() { + return getMap().toString(); + } + + // ----------------------------------------------------------------------- + + /** + * Wrapped collection to handle add and remove on the collection returned + * by get(object). + *

              + * Currently, the wrapped collection is not cached and has to be retrieved + * from the underlying map. This is safe, but not very efficient and + * should be improved in subsequent releases. For this purpose, the + * scope of this collection is set to package private to simplify later + * refactoring. + */ + class WrappedCollection implements Collection { + + protected final K key; + + public WrappedCollection(final K key) { + this.key = key; + } + + protected Collection getMapping() { + return getMap().get(key); + } + + @Override + public boolean add(V value) { + Collection coll = getMapping(); + if (coll == null) { + coll = createCollection(); + AbstractMultiValuedMap.this.map.put(key, coll); + } + return coll.add(value); + } + + @Override + public boolean addAll(Collection other) { + Collection coll = getMapping(); + if (coll == null) { + coll = createCollection(); + AbstractMultiValuedMap.this.map.put(key, coll); + } + return coll.addAll(other); + } + + @Override + public void clear() { + final Collection coll = getMapping(); + if (coll != null) { + coll.clear(); + AbstractMultiValuedMap.this.remove(key); + } + } + + @Override + @SuppressWarnings("unchecked") + public Iterator iterator() { + final Collection coll = getMapping(); + if (coll == null) { + return IteratorUtils.EMPTY_ITERATOR; + } + return new ValuesIterator(key); + } + + @Override + public int size() { + final Collection coll = getMapping(); + return coll == null ? 0 : coll.size(); + } + + @Override + public boolean contains(Object obj) { + final Collection coll = getMapping(); + return coll == null ? false : coll.contains(obj); + } + + @Override + public boolean containsAll(Collection other) { + final Collection coll = getMapping(); + return coll == null ? false : coll.containsAll(other); + } + + @Override + public boolean isEmpty() { + final Collection coll = getMapping(); + return coll == null ? true : coll.isEmpty(); + } + + @Override + public boolean remove(Object item) { + final Collection coll = getMapping(); + if (coll == null) { + return false; + } + + boolean result = coll.remove(item); + if (coll.isEmpty()) { + AbstractMultiValuedMap.this.remove(key); + } + return result; + } + + @Override + public boolean removeAll(Collection c) { + final Collection coll = getMapping(); + if (coll == null) { + return false; + } + + boolean result = coll.removeAll(c); + if (coll.isEmpty()) { + AbstractMultiValuedMap.this.remove(key); + } + return result; + } + + @Override + public boolean retainAll(Collection c) { + final Collection coll = getMapping(); + if (coll == null) { + return false; + } + + boolean result = coll.retainAll(c); + if (coll.isEmpty()) { + AbstractMultiValuedMap.this.remove(key); + } + return result; + } + + @Override + public Object[] toArray() { + final Collection coll = getMapping(); + if (coll == null) { + return CollectionUtils.EMPTY_COLLECTION.toArray(); + } + return coll.toArray(); + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + final Collection coll = getMapping(); + if (coll == null) { + return (T[]) CollectionUtils.EMPTY_COLLECTION.toArray(a); + } + return coll.toArray(a); + } + + @Override + public String toString() { + final Collection coll = getMapping(); + if (coll == null) { + return CollectionUtils.EMPTY_COLLECTION.toString(); + } + return coll.toString(); + } + + } + + /** + * Inner class that provides a MultiSet keys view. + */ + private class KeysMultiSet extends AbstractMultiSet { + + @Override + public boolean contains(Object o) { + return getMap().containsKey(o); + } + + @Override + public boolean isEmpty() { + return getMap().isEmpty(); + } + + @Override + public int size() { + return AbstractMultiValuedMap.this.size(); + } + + @Override + protected int uniqueElements() { + return getMap().size(); + } + + @Override + public int getCount(Object object) { + int count = 0; + Collection col = AbstractMultiValuedMap.this.getMap().get(object); + if (col != null) { + count = col.size(); + } + return count; + } + + @Override + protected Iterator> createEntrySetIterator() { + final MapEntryTransformer transformer = new MapEntryTransformer(); + return IteratorUtils.transformedIterator(map.entrySet().iterator(), transformer); + } + + private final class MapEntryTransformer + implements Transformer>, MultiSet.Entry> { + @Override + public MultiSet.Entry transform(final Map.Entry> mapEntry) { + return new AbstractMultiSet.AbstractEntry() { + @Override + public K getElement() { + return mapEntry.getKey(); + } + + @Override + public int getCount() { + return mapEntry.getValue().size(); + } + }; + } + } + } + + /** + * Inner class that provides the Entry view + */ + private class EntryValues extends AbstractCollection> { + + @Override + public Iterator> iterator() { + return new LazyIteratorChain>() { + + final Collection keysCol = new ArrayList(getMap().keySet()); + final Iterator keyIterator = keysCol.iterator(); + + @Override + protected Iterator> nextIterator(int count) { + if (!keyIterator.hasNext()) { + return null; + } + final K key = keyIterator.next(); + final Transformer> entryTransformer = new Transformer>() { + + @Override + public Entry transform(final V input) { + return new MultiValuedMapEntry(key, input); + } + + }; + return new TransformIterator>(new ValuesIterator(key), entryTransformer); + } + }; + } + + @Override + public int size() { + return AbstractMultiValuedMap.this.size(); + } + + } + + /** + * Inner class for MultiValuedMap Entries. + */ + private class MultiValuedMapEntry extends AbstractMapEntry { + + public MultiValuedMapEntry(K key, V value) { + super(key, value); + } + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + } + + /** + * Inner class for MapIterator. + */ + private class MultiValuedMapIterator implements MapIterator { + + private final Iterator> it; + + private Entry current = null; + + public MultiValuedMapIterator() { + this.it = AbstractMultiValuedMap.this.entries().iterator(); + } + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public K next() { + current = it.next(); + return current.getKey(); + } + + @Override + public K getKey() { + if (current == null) { + throw new IllegalStateException(); + } + return current.getKey(); + } + + @Override + public V getValue() { + if (current == null) { + throw new IllegalStateException(); + } + return current.getValue(); + } + + @Override + public void remove() { + it.remove(); + } + + @Override + public V setValue(V value) { + if (current == null) { + throw new IllegalStateException(); + } + return current.setValue(value); + } + + } + + /** + * Inner class that provides the values view. + */ + private class Values extends AbstractCollection { + @Override + public Iterator iterator() { + final IteratorChain chain = new IteratorChain(); + for (final K k : keySet()) { + chain.addIterator(new ValuesIterator(k)); + } + return chain; + } + + @Override + public int size() { + return AbstractMultiValuedMap.this.size(); + } + + @Override + public void clear() { + AbstractMultiValuedMap.this.clear(); + } + } + + /** + * Inner class that provides the values iterator. + */ + private class ValuesIterator implements Iterator { + private final Object key; + private final Collection values; + private final Iterator iterator; + + public ValuesIterator(final Object key) { + this.key = key; + this.values = getMap().get(key); + this.iterator = values.iterator(); + } + + @Override + public void remove() { + iterator.remove(); + if (values.isEmpty()) { + AbstractMultiValuedMap.this.remove(key); + } + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public V next() { + return iterator.next(); + } + } + + /** + * Inner class that provides the AsMap view. + */ + private class AsMap extends AbstractMap> { + final transient Map> decoratedMap; + + AsMap(final Map> map) { + this.decoratedMap = map; + } + + @Override + public Set>> entrySet() { + return new AsMapEntrySet(); + } + + @Override + public boolean containsKey(Object key) { + return decoratedMap.containsKey(key); + } + + @Override + public Collection get(Object key) { + Collection collection = decoratedMap.get(key); + if (collection == null) { + return null; + } + @SuppressWarnings("unchecked") + K k = (K) key; + return wrappedCollection(k); + } + + @Override + public Set keySet() { + return AbstractMultiValuedMap.this.keySet(); + } + + @Override + public int size() { + return decoratedMap.size(); + } + + @Override + public Collection remove(Object key) { + Collection collection = decoratedMap.remove(key); + if (collection == null) { + return null; + } + + final Collection output = createCollection(); + output.addAll(collection); + collection.clear(); + return output; + } + + @Override + public boolean equals(Object object) { + return this == object || decoratedMap.equals(object); + } + + @Override + public int hashCode() { + return decoratedMap.hashCode(); + } + + @Override + public String toString() { + return decoratedMap.toString(); + } + + @Override + public void clear() { + AbstractMultiValuedMap.this.clear(); + } + + class AsMapEntrySet extends AbstractSet>> { + + @Override + public Iterator>> iterator() { + return new AsMapEntrySetIterator(decoratedMap.entrySet().iterator()); + } + + @Override + public int size() { + return AsMap.this.size(); + } + + @Override + public void clear() { + AsMap.this.clear(); + } + + @Override + public boolean contains(Object o) { + return decoratedMap.entrySet().contains(o); + } + + @Override + public boolean remove(Object o) { + if (!contains(o)) { + return false; + } + Map.Entry entry = (Map.Entry) o; + AbstractMultiValuedMap.this.remove(entry.getKey()); + return true; + } + } + + /** + * EntrySet iterator for the asMap view. + */ + class AsMapEntrySetIterator extends AbstractIteratorDecorator>> { + + AsMapEntrySetIterator(Iterator>> iterator) { + super(iterator); + } + + @Override + public Map.Entry> next() { + final Map.Entry> entry = super.next(); + final K key = entry.getKey(); + return new UnmodifiableMapEntry>(key, wrappedCollection(key)); + } + } + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * @param out the output stream + * @throws IOException any of the usual I/O related exceptions + */ + protected void doWriteObject(final ObjectOutputStream out) throws IOException { + out.writeInt(map.size()); + for (final Map.Entry> entry : map.entrySet()) { + out.writeObject(entry.getKey()); + out.writeInt(entry.getValue().size()); + for (final V value : entry.getValue()) { + out.writeObject(value); + } + } + } + + /** + * Read the map in using a custom routine. + * @param in the input stream + * @throws IOException any of the usual I/O related exceptions + * @throws ClassNotFoundException if the stream contains an object which class can not be loaded + * @throws ClassCastException if the stream does not contain the correct objects + */ + protected void doReadObject(final ObjectInputStream in) + throws IOException, ClassNotFoundException { + final int entrySize = in.readInt(); + for (int i = 0; i < entrySize; i++) { + @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect + final K key = (K) in.readObject(); + final Collection values = get(key); + final int valueSize = in.readInt(); + for (int j = 0; j < valueSize; j++) { + @SuppressWarnings("unchecked") // see above + V value = (V) in.readObject(); + values.add(value); + } + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java new file mode 100644 index 000000000..8962ba10f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractMultiValuedMapDecorator.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multimap; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.MultiSet; +import org.apache.commons.collections4.MultiValuedMap; + +/** + * Decorates another MultiValuedMap to provide additional behaviour. + *

              + * Each method call made on this MultiValuedMap is forwarded to the + * decorated MultiValuedMap. This class is used as a framework to build + * to extensions such as synchronized and unmodifiable behaviour. + * + * @param the type of key elements + * @param the type of value elements + * + * @since 4.1 + * @version $Id: AbstractMultiValuedMapDecorator.java 1715302 2015-11-19 23:08:01Z tn $ + */ +public abstract class AbstractMultiValuedMapDecorator + implements MultiValuedMap, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 20150612L; + + /** MultiValuedMap to decorate */ + private final MultiValuedMap map; + + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @throws NullPointerException if the map is null + */ + protected AbstractMultiValuedMapDecorator(final MultiValuedMap map) { + if (map == null) { + throw new NullPointerException("MultiValuedMap must not be null."); + } + this.map = map; + } + + // ----------------------------------------------------------------------- + /** + * The decorated multi-valued map. + * + * @return the map to decorate + */ + protected MultiValuedMap decorated() { + return map; + } + + // ----------------------------------------------------------------------- + @Override + public int size() { + return decorated().size(); + } + + @Override + public boolean isEmpty() { + return decorated().isEmpty(); + } + + @Override + public boolean containsKey(final Object key) { + return decorated().containsKey(key); + } + + @Override + public boolean containsValue(final Object value) { + return decorated().containsValue(value); + } + + @Override + public boolean containsMapping(final Object key, final Object value) { + return decorated().containsMapping(key, value); + } + + @Override + public Collection get(final K key) { + return decorated().get(key); + } + + @Override + public Collection remove(final Object key) { + return decorated().remove(key); + } + + @Override + public boolean removeMapping(final Object key, final Object item) { + return decorated().removeMapping(key, item); + } + + @Override + public void clear() { + decorated().clear(); + } + + @Override + public boolean put(K key, V value) { + return decorated().put(key, value); + } + + @Override + public Set keySet() { + return decorated().keySet(); + } + + @Override + public Collection> entries() { + return decorated().entries(); + } + + @Override + public MultiSet keys() { + return decorated().keys(); + } + + @Override + public Collection values() { + return decorated().values(); + } + + @Override + public Map> asMap() { + return decorated().asMap(); + } + + @Override + public boolean putAll(K key, Iterable values) { + return decorated().putAll(key, values); + } + + @Override + public boolean putAll(Map map) { + return decorated().putAll(map); + } + + @Override + public boolean putAll(MultiValuedMap map) { + return decorated().putAll(map); + } + + @Override + public MapIterator mapIterator() { + return decorated().mapIterator(); + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + return decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + @Override + public String toString() { + return decorated().toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractSetValuedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractSetValuedMap.java new file mode 100644 index 000000000..c13579d31 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/AbstractSetValuedMap.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multimap; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.SetUtils; +import org.apache.commons.collections4.SetValuedMap; + +/** + * Abstract implementation of the {@link SetValuedMap} interface to simplify the + * creation of subclass implementations. + *

              + * Subclasses specify a Map implementation to use as the internal storage and + * the Set implementation to use as values. + * + * @since 4.1 + * @version $Id: AbstractSetValuedMap.java 1715695 2015-11-22 21:11:49Z tn $ + */ +public abstract class AbstractSetValuedMap extends AbstractMultiValuedMap + implements SetValuedMap { + + /** + * Constructor needed for subclass serialisation. + */ + protected AbstractSetValuedMap() { + super(); + } + + /** + * A constructor that wraps, not copies + * + * @param map the map to wrap, must not be null + * @throws NullPointerException if the map is null + */ + protected AbstractSetValuedMap(Map> map) { + super(map); + } + + // ----------------------------------------------------------------------- + @Override + @SuppressWarnings("unchecked") + protected Map> getMap() { + return (Map>) super.getMap(); + } + + /** + * Creates a new value collection using the provided factory. + * @return a new list + */ + @Override + protected abstract Set createCollection(); + + // ----------------------------------------------------------------------- + /** + * Gets the set of values associated with the specified key. This would + * return an empty set in case the mapping is not present + * + * @param key the key to retrieve + * @return the Set of values, will return an empty + * Set for no mapping + */ + @Override + public Set get(final K key) { + return wrappedCollection(key); + } + + @Override + Set wrappedCollection(final K key) { + return new WrappedSet(key); + } + + /** + * Removes all values associated with the specified key. + *

              + * A subsequent get(Object) would return an empty set. + * + * @param key the key to remove values from + * @return the Set of values removed, will return an empty, + * unmodifiable set for no mapping found. + */ + @Override + public Set remove(Object key) { + return SetUtils.emptyIfNull(getMap().remove(key)); + } + + // ----------------------------------------------------------------------- + /** + * Wrapped set to handle add and remove on the collection returned by + * {@code get(Object)}. + */ + private class WrappedSet extends WrappedCollection implements Set { + + public WrappedSet(final K key) { + super(key); + } + + @Override + public boolean equals(Object other) { + final Set set = (Set) getMapping(); + if (set == null) { + return Collections.emptySet().equals(other); + } + if (!(other instanceof Set)) { + return false; + } + Set otherSet = (Set) other; + return SetUtils.isEqualSet(set, otherSet); + } + + @Override + public int hashCode() { + final Set set = (Set) getMapping(); + return SetUtils.hashCodeForSet(set); + } + + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multimap/ArrayListValuedHashMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/ArrayListValuedHashMap.java new file mode 100644 index 000000000..4c645ccaa --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/ArrayListValuedHashMap.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multimap; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.collections4.MultiValuedMap; + +/** + * Implements a {@code ListValuedMap}, using a {@link HashMap} to provide data + * storage and {@link ArrayList}s as value collections. This is the standard + * implementation of a ListValuedMap. + *

              + * Note that ArrayListValuedHashMap is not synchronized and is not + * thread-safe. If you wish to use this map from multiple threads + * concurrently, you must use appropriate synchronization. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + * + * @since 4.1 + * @version $Id: ArrayListValuedHashMap.java 1715492 2015-11-21 10:09:31Z tn $ + */ +public class ArrayListValuedHashMap extends AbstractListValuedMap + implements Serializable { + + /** Serialization Version */ + private static final long serialVersionUID = 20151118L; + + /** + * The initial map capacity used when none specified in constructor. + */ + private static final int DEFAULT_INITIAL_MAP_CAPACITY = 16; + + /** + * The initial list capacity when using none specified in constructor. + */ + private static final int DEFAULT_INITIAL_LIST_CAPACITY = 3; + + /** + * The initial list capacity when creating a new value collection. + */ + private final int initialListCapacity; + + /** + * Creates an empty ArrayListValuedHashMap with the default initial + * map capacity (16) and the default initial list capacity (3). + */ + public ArrayListValuedHashMap() { + this(DEFAULT_INITIAL_MAP_CAPACITY, DEFAULT_INITIAL_LIST_CAPACITY); + } + + /** + * Creates an empty ArrayListValuedHashMap with the default initial + * map capacity (16) and the specified initial list capacity. + * + * @param initialListCapacity the initial capacity used for value collections + */ + public ArrayListValuedHashMap(int initialListCapacity) { + this(DEFAULT_INITIAL_MAP_CAPACITY, initialListCapacity); + } + + /** + * Creates an empty ArrayListValuedHashMap with the specified initial + * map and list capacities. + * + * @param initialMapCapacity the initial hashmap capacity + * @param initialListCapacity the initial capacity used for value collections + */ + public ArrayListValuedHashMap(int initialMapCapacity, int initialListCapacity) { + super(new HashMap>(initialMapCapacity)); + this.initialListCapacity = initialListCapacity; + } + + /** + * Creates an ArrayListValuedHashMap copying all the mappings of the given map. + * + * @param map a MultiValuedMap to copy into this map + */ + public ArrayListValuedHashMap(final MultiValuedMap map) { + this(map.size(), DEFAULT_INITIAL_LIST_CAPACITY); + super.putAll(map); + } + + /** + * Creates an ArrayListValuedHashMap copying all the mappings of the given map. + * + * @param map a Map to copy into this map + */ + public ArrayListValuedHashMap(final Map map) { + this(map.size(), DEFAULT_INITIAL_LIST_CAPACITY); + super.putAll(map); + } + + // ----------------------------------------------------------------------- + @Override + protected ArrayList createCollection() { + return new ArrayList(initialListCapacity); + } + + // ----------------------------------------------------------------------- + /** + * Trims the capacity of all value collections to their current size. + */ + public void trimToSize() { + for (Collection coll : getMap().values()) { + final ArrayList list = (ArrayList) coll; + list.trimToSize(); + } + } + + // ----------------------------------------------------------------------- + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + doWriteObject(oos); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + setMap(new HashMap>()); + doReadObject(ois); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multimap/HashSetValuedHashMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/HashSetValuedHashMap.java new file mode 100644 index 000000000..3a9396f5a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/HashSetValuedHashMap.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multimap; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import org.apache.commons.collections4.MultiValuedMap; + +/** + * Implements a {@code SetValuedMap}, using a {@link HashMap} to provide data + * storage and {@link HashSet}s as value collections. This is the standard + * implementation of a SetValuedMap. + *

              + * Note that HashSetValuedHashMap is not synchronized and is not + * thread-safe. If you wish to use this map from multiple threads + * concurrently, you must use appropriate synchronization. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + * + * @since 4.1 + * @version $Id: HashSetValuedHashMap.java 1715492 2015-11-21 10:09:31Z tn $ + */ +public class HashSetValuedHashMap extends AbstractSetValuedMap + implements Serializable { + + /** Serialization Version */ + private static final long serialVersionUID = 20151118L; + + /** + * The initial map capacity used when none specified in constructor. + */ + private static final int DEFAULT_INITIAL_MAP_CAPACITY = 16; + + /** + * The initial set capacity when using none specified in constructor. + */ + private static final int DEFAULT_INITIAL_SET_CAPACITY = 3; + + /** + * The initial list capacity when creating a new value collection. + */ + private final int initialSetCapacity; + + /** + * Creates an empty HashSetValuedHashMap with the default initial + * map capacity (16) and the default initial set capacity (3). + */ + public HashSetValuedHashMap() { + this(DEFAULT_INITIAL_MAP_CAPACITY, DEFAULT_INITIAL_SET_CAPACITY); + } + + /** + * Creates an empty HashSetValuedHashMap with the default initial + * map capacity (16) and the specified initial set capacity. + * + * @param initialSetCapacity the initial capacity used for value collections + */ + public HashSetValuedHashMap(int initialSetCapacity) { + this(DEFAULT_INITIAL_MAP_CAPACITY, initialSetCapacity); + } + + /** + * Creates an empty HashSetValuedHashMap with the specified initial + * map and list capacities. + * + * @param initialMapCapacity the initial hashmap capacity + * @param initialSetCapacity the initial capacity used for value collections + */ + public HashSetValuedHashMap(int initialMapCapacity, int initialSetCapacity) { + super(new HashMap>(initialMapCapacity)); + this.initialSetCapacity = initialSetCapacity; + } + + /** + * Creates an HashSetValuedHashMap copying all the mappings of the given map. + * + * @param map a MultiValuedMap to copy into this map + */ + public HashSetValuedHashMap(final MultiValuedMap map) { + this(map.size(), DEFAULT_INITIAL_SET_CAPACITY); + super.putAll(map); + } + + /** + * Creates an HashSetValuedHashMap copying all the mappings of the given map. + * + * @param map a Map to copy into this map + */ + public HashSetValuedHashMap(final Map map) { + this(map.size(), DEFAULT_INITIAL_SET_CAPACITY); + super.putAll(map); + } + + // ----------------------------------------------------------------------- + @Override + protected HashSet createCollection() { + return new HashSet(initialSetCapacity); + } + + // ----------------------------------------------------------------------- + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + doWriteObject(oos); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + setMap(new HashMap>()); + doReadObject(ois); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multimap/TransformedMultiValuedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/TransformedMultiValuedMap.java new file mode 100644 index 000000000..f4131f051 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/TransformedMultiValuedMap.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multimap; + +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.FluentIterable; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.Transformer; + +/** + * Decorates another MultiValuedMap to transform objects that are added. + *

              + * This class affects the MultiValuedMap put methods. Thus objects must be + * removed or searched for using their transformed form. For example, if the + * transformation converts Strings to Integers, you must use the Integer form to + * remove objects. + *

              + * Note that TransformedMultiValuedMap is not synchronized and is not thread-safe. + * + * @since 4.1 + * @version $Id: TransformedMultiValuedMap.java 1715302 2015-11-19 23:08:01Z tn $ + */ +public class TransformedMultiValuedMap extends AbstractMultiValuedMapDecorator { + + /** Serialization Version */ + private static final long serialVersionUID = 20150612L; + + /** The key transformer */ + private final Transformer keyTransformer; + + /** The value transformer */ + private final Transformer valueTransformer; + + /** + * Factory method to create a transforming MultiValuedMap. + *

              + * If there are any elements already in the map being decorated, they are + * NOT transformed. Contrast this with + * {@link #transformedMap(MultiValuedMap, Transformer, Transformer)}. + * + * @param the key type + * @param the value type + * @param map the MultiValuedMap to decorate, may not be null + * @param keyTransformer the transformer to use for key conversion, null means no conversion + * @param valueTransformer the transformer to use for value conversion, null means no conversion + * @return a new transformed MultiValuedMap + * @throws NullPointerException if map is null + */ + public static TransformedMultiValuedMap transformingMap(final MultiValuedMap map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + return new TransformedMultiValuedMap(map, keyTransformer, valueTransformer); + } + + /** + * Factory method to create a transforming MultiValuedMap that will + * transform existing contents of the specified map. + *

              + * If there are any elements already in the map being decorated, they will + * be transformed by this method. Contrast this with + * {@link #transformingMap(MultiValuedMap, Transformer, Transformer)}. + * + * @param the key type + * @param the value type + * @param map the MultiValuedMap to decorate, may not be null + * @param keyTransformer the transformer to use for key conversion, null means no conversion + * @param valueTransformer the transformer to use for value conversion, null means no conversion + * @return a new transformed MultiValuedMap + * @throws NullPointerException if map is null + */ + public static TransformedMultiValuedMap transformedMap(final MultiValuedMap map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + final TransformedMultiValuedMap decorated = + new TransformedMultiValuedMap(map, keyTransformer, valueTransformer); + if (!map.isEmpty()) { + final MultiValuedMap mapCopy = new ArrayListValuedHashMap(map); + decorated.clear(); + decorated.putAll(mapCopy); + } + return decorated; + } + + // ----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the collection being decorated, they + * are NOT transformed. + * + * @param map the MultiValuedMap to decorate, may not be null + * @param keyTransformer the transformer to use for key conversion, null means no conversion + * @param valueTransformer the transformer to use for value conversion, null means no conversion + * @throws NullPointerException if map is null + */ + protected TransformedMultiValuedMap(final MultiValuedMap map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + super(map); + this.keyTransformer = keyTransformer; + this.valueTransformer = valueTransformer; + } + + /** + * Transforms a key. + *

              + * The transformer itself may throw an exception if necessary. + * + * @param object the object to transform + * @return the transformed object + */ + protected K transformKey(final K object) { + if (keyTransformer == null) { + return object; + } + return keyTransformer.transform(object); + } + + /** + * Transforms a value. + *

              + * The transformer itself may throw an exception if necessary. + * + * @param object the object to transform + * @return the transformed object + */ + protected V transformValue(final V object) { + if (valueTransformer == null) { + return object; + } + return valueTransformer.transform(object); + } + + @Override + public boolean put(final K key, final V value) { + return decorated().put(transformKey(key), transformValue(value)); + } + + @Override + public boolean putAll(final K key, final Iterable values) { + if (values == null) { + throw new NullPointerException("Values must not be null."); + } + + final Iterable transformedValues = FluentIterable.of(values).transform(valueTransformer); + final Iterator it = transformedValues.iterator(); + return it.hasNext() && CollectionUtils.addAll(decorated().get(transformKey(key)), it); + } + + @Override + public boolean putAll(final Map map) { + if (map == null) { + throw new NullPointerException("Map must not be null."); + } + boolean changed = false; + for (Map.Entry entry : map.entrySet()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + @Override + public boolean putAll(final MultiValuedMap map) { + if (map == null) { + throw new NullPointerException("Map must not be null."); + } + boolean changed = false; + for (Map.Entry entry : map.entries()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java new file mode 100644 index 000000000..1166c2af9 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/UnmodifiableMultiValuedMap.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multimap; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.MultiSet; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.collection.UnmodifiableCollection; +import org.apache.commons.collections4.iterators.UnmodifiableMapIterator; +import org.apache.commons.collections4.map.UnmodifiableMap; +import org.apache.commons.collections4.multiset.UnmodifiableMultiSet; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another {@link MultiValuedMap} to ensure it can't be altered. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @param the type of key elements + * @param the type of value elements + * + * @since 4.1 + * @version $Id: UnmodifiableMultiValuedMap.java 1685299 2015-06-13 18:27:11Z tn $ + */ +public final class UnmodifiableMultiValuedMap + extends AbstractMultiValuedMapDecorator implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = 20150612L; + + /** + * Factory method to create an unmodifiable MultiValuedMap. + *

              + * If the map passed in is already unmodifiable, it is returned. + * + * @param the type of key elements + * @param the type of value elements + * @param map the map to decorate, may not be null + * @return an unmodifiable MultiValuedMap + * @throws NullPointerException if map is null + */ + @SuppressWarnings("unchecked") + public static UnmodifiableMultiValuedMap unmodifiableMultiValuedMap( + MultiValuedMap map) { + if (map instanceof Unmodifiable) { + return (UnmodifiableMultiValuedMap) map; + } + return new UnmodifiableMultiValuedMap(map); + } + + /** + * Constructor that wraps (not copies). + * + * @param map the MultiValuedMap to decorate, may not be null + * @throws NullPointerException if the map is null + */ + @SuppressWarnings("unchecked") + private UnmodifiableMultiValuedMap(final MultiValuedMap map) { + super((MultiValuedMap) map); + } + + @Override + public Collection remove(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeMapping(final Object key, final Object item) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection get(final K key) { + return UnmodifiableCollection.unmodifiableCollection(decorated().get(key)); + } + + @Override + public boolean put(final K key, final V value) { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + return UnmodifiableSet.unmodifiableSet(decorated().keySet()); + } + + @Override + public Collection> entries() { + return UnmodifiableCollection.unmodifiableCollection(decorated().entries()); + } + + @Override + public MultiSet keys() { + return UnmodifiableMultiSet.unmodifiableMultiSet(decorated().keys()); + } + + @Override + public Collection values() { + return UnmodifiableCollection.unmodifiableCollection(decorated().values()); + } + + @Override + public Map> asMap() { + return UnmodifiableMap.unmodifiableMap(decorated().asMap()); + } + + @Override + public MapIterator mapIterator() { + return UnmodifiableMapIterator.unmodifiableMapIterator(decorated().mapIterator()); + } + + @Override + public boolean putAll(final K key, final Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(final Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(final MultiValuedMap map) { + throw new UnsupportedOperationException(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multimap/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/package-info.java new file mode 100644 index 000000000..ee2a4fdcd --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multimap/package-info.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the {@link org.apache.commons.collections4.MultiValuedMap} interfaces. + * A MultiValuedMap holds a collection of values against each key. + *

              + * The following implementations are provided in the package: + *

                + *
              • ArrayListValuedHashMap - ListValuedMap implementation using a HashMap/ArrayList + *
              • HashSetValuedHashMap - SetValuedMap implementation using a HashMap/HashSet + *
              + *

              + * The following decorators are provided in the package: + *

                + *
              • Transformed - transforms elements added to the MultiValuedMap + *
              • Unmodifiable - ensures the collection cannot be altered + *
              + * + * @version $Id: package-info.java 1715302 2015-11-19 23:08:01Z tn $ + */ +package org.apache.commons.collections4.multimap; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMapMultiSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMapMultiSet.java new file mode 100644 index 000000000..0b39f0672 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMapMultiSet.java @@ -0,0 +1,564 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multiset; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Array; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.collections4.MultiSet; +import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; + +/** + * Abstract implementation of the {@link MultiSet} interface to simplify the + * creation of subclass implementations. + *

              + * Subclasses specify a Map implementation to use as the internal storage. The + * map will be used to map multiset elements to a number; the number represents the + * number of occurrences of that element in the multiset. + * + * @since 4.1 + * @version $Id: AbstractMapMultiSet.java 1715563 2015-11-21 20:13:35Z tn $ + */ +public abstract class AbstractMapMultiSet extends AbstractMultiSet { + + /** The map to use to store the data */ + private transient Map map; + /** The current total size of the multiset */ + private transient int size; + /** The modification count for fail fast iterators */ + private transient int modCount; + + /** + * Constructor needed for subclass serialisation. + */ + protected AbstractMapMultiSet() { + super(); + } + + /** + * Constructor that assigns the specified Map as the backing store. The map + * must be empty and non-null. + * + * @param map the map to assign + */ + protected AbstractMapMultiSet(final Map map) { + super(); + this.map = map; + } + + /** + * Utility method for implementations to access the map that backs this multiset. + * Not intended for interactive use outside of subclasses. + * + * @return the map being used by the MultiSet + */ + protected Map getMap() { + return map; + } + + /** + * Sets the map being wrapped. + *

              + * NOTE: this method should only be used during deserialization + * + * @param map the map to wrap + */ + protected void setMap(Map map) { + this.map = map; + } + + //----------------------------------------------------------------------- + /** + * Returns the number of elements in this multiset. + * + * @return current size of the multiset + */ + @Override + public int size() { + return size; + } + + /** + * Returns true if the underlying map is empty. + * + * @return true if multiset is empty + */ + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * Returns the number of occurrence of the given element in this multiset by + * looking up its count in the underlying map. + * + * @param object the object to search for + * @return the number of occurrences of the object, zero if not found + */ + @Override + public int getCount(final Object object) { + final MutableInteger count = map.get(object); + if (count != null) { + return count.value; + } + return 0; + } + + //----------------------------------------------------------------------- + /** + * Determines if the multiset contains the given element by checking if the + * underlying map contains the element as a key. + * + * @param object the object to search for + * @return true if the multiset contains the given element + */ + @Override + public boolean contains(final Object object) { + return map.containsKey(object); + } + + //----------------------------------------------------------------------- + /** + * Gets an iterator over the multiset elements. Elements present in the + * MultiSet more than once will be returned repeatedly. + * + * @return the iterator + */ + @Override + public Iterator iterator() { + return new MapBasedMultiSetIterator(this); + } + + /** + * Inner class iterator for the MultiSet. + */ + private static class MapBasedMultiSetIterator implements Iterator { + private final AbstractMapMultiSet parent; + private final Iterator> entryIterator; + private Map.Entry current; + private int itemCount; + private final int mods; + private boolean canRemove; + + /** + * Constructor. + * + * @param parent the parent multiset + */ + public MapBasedMultiSetIterator(final AbstractMapMultiSet parent) { + this.parent = parent; + this.entryIterator = parent.map.entrySet().iterator(); + this.current = null; + this.mods = parent.modCount; + this.canRemove = false; + } + + /** {@inheritDoc} */ + @Override + public boolean hasNext() { + return itemCount > 0 || entryIterator.hasNext(); + } + + /** {@inheritDoc} */ + @Override + public E next() { + if (parent.modCount != mods) { + throw new ConcurrentModificationException(); + } + if (itemCount == 0) { + current = entryIterator.next(); + itemCount = current.getValue().value; + } + canRemove = true; + itemCount--; + return current.getKey(); + } + + /** {@inheritDoc} */ + @Override + public void remove() { + if (parent.modCount != mods) { + throw new ConcurrentModificationException(); + } + if (canRemove == false) { + throw new IllegalStateException(); + } + final MutableInteger mut = current.getValue(); + if (mut.value > 1) { + mut.value--; + } else { + entryIterator.remove(); + } + parent.size--; + canRemove = false; + } + } + + //----------------------------------------------------------------------- + @Override + public int add(final E object, final int occurrences) { + if (occurrences < 0) { + throw new IllegalArgumentException("Occurrences must not be negative."); + } + + final MutableInteger mut = map.get(object); + int oldCount = mut != null ? mut.value : 0; + + if (occurrences > 0) { + modCount++; + size += occurrences; + if (mut == null) { + map.put(object, new MutableInteger(occurrences)); + } else { + mut.value += occurrences; + } + } + return oldCount; + } + + //----------------------------------------------------------------------- + /** + * Clears the multiset by clearing the underlying map. + */ + @Override + public void clear() { + modCount++; + map.clear(); + size = 0; + } + + @Override + public int remove(final Object object, final int occurrences) { + if (occurrences < 0) { + throw new IllegalArgumentException("Occurrences must not be negative."); + } + + final MutableInteger mut = map.get(object); + if (mut == null) { + return 0; + } + int oldCount = mut.value; + if (occurrences > 0) { + modCount++; + if (occurrences < mut.value) { + mut.value -= occurrences; + size -= occurrences; + } else { + map.remove(object); + size -= mut.value; + } + } + return oldCount; + } + + //----------------------------------------------------------------------- + /** + * Mutable integer class for storing the data. + */ + protected static class MutableInteger { + /** The value of this mutable. */ + protected int value; + + /** + * Constructor. + * @param value the initial value + */ + MutableInteger(final int value) { + this.value = value; + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableInteger == false) { + return false; + } + return ((MutableInteger) obj).value == value; + } + + @Override + public int hashCode() { + return value; + } + } + + //----------------------------------------------------------------------- + @Override + protected Iterator createUniqueSetIterator() { + return new UniqueSetIterator(getMap().keySet().iterator(), this); + } + + @Override + protected int uniqueElements() { + return map.size(); + } + + @Override + protected Iterator> createEntrySetIterator() { + return new EntrySetIterator(map.entrySet().iterator(), this); + } + + //----------------------------------------------------------------------- + /** + * Inner class UniqueSetIterator. + */ + protected static class UniqueSetIterator extends AbstractIteratorDecorator { + + /** The parent multiset */ + protected final AbstractMapMultiSet parent; + + /** The last returned element */ + protected E lastElement = null; + + /** Whether remove is allowed at present */ + protected boolean canRemove = false; + + /** + * Constructor. + * @param iterator the iterator to decorate + * @param parent the parent multiset + */ + protected UniqueSetIterator(final Iterator iterator, final AbstractMapMultiSet parent) { + super(iterator); + this.parent = parent; + } + + @Override + public E next() { + lastElement = super.next(); + canRemove = true; + return lastElement; + } + + @Override + public void remove() { + if (canRemove == false) { + throw new IllegalStateException("Iterator remove() can only be called once after next()"); + } + final int count = parent.getCount(lastElement); + super.remove(); + parent.remove(lastElement, count); + lastElement = null; + canRemove = false; + } + } + + /** + * Inner class EntrySetIterator. + */ + protected static class EntrySetIterator implements Iterator> { + + /** The parent map */ + protected final AbstractMapMultiSet parent; + + protected final Iterator> decorated; + + /** The last returned entry */ + protected Entry last = null; + + /** Whether remove is allowed at present */ + protected boolean canRemove = false; + + /** + * Constructor. + * @param iterator the iterator to decorate + * @param parent the parent multiset + */ + protected EntrySetIterator(final Iterator> iterator, + final AbstractMapMultiSet parent) { + this.decorated = iterator; + this.parent = parent; + } + + @Override + public boolean hasNext() { + return decorated.hasNext(); + } + + @Override + public Entry next() { + last = new MultiSetEntry(decorated.next()); + canRemove = true; + return last; + } + + @Override + public void remove() { + if (canRemove == false) { + throw new IllegalStateException("Iterator remove() can only be called once after next()"); + } + decorated.remove(); + last = null; + canRemove = false; + } + } + + /** + * Inner class MultiSetEntry. + */ + protected static class MultiSetEntry extends AbstractEntry { + + protected final Map.Entry parentEntry; + + /** + * Constructor. + * @param parentEntry the entry to decorate + */ + protected MultiSetEntry(final Map.Entry parentEntry) { + this.parentEntry = parentEntry; + } + + @Override + public E getElement() { + return parentEntry.getKey(); + } + + @Override + public int getCount() { + return parentEntry.getValue().value; + } + } + + //----------------------------------------------------------------------- + /** + * Write the multiset out using a custom routine. + * @param out the output stream + * @throws IOException any of the usual I/O related exceptions + */ + @Override + protected void doWriteObject(final ObjectOutputStream out) throws IOException { + out.writeInt(map.size()); + for (final Map.Entry entry : map.entrySet()) { + out.writeObject(entry.getKey()); + out.writeInt(entry.getValue().value); + } + } + + /** + * Read the multiset in using a custom routine. + * @param in the input stream + * @throws IOException any of the usual I/O related exceptions + * @throws ClassNotFoundException if the stream contains an object which class can not be loaded + * @throws ClassCastException if the stream does not contain the correct objects + */ + @Override + protected void doReadObject(final ObjectInputStream in) + throws IOException, ClassNotFoundException { + final int entrySize = in.readInt(); + for (int i = 0; i < entrySize; i++) { + @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect + final E obj = (E) in.readObject(); + final int count = in.readInt(); + map.put(obj, new MutableInteger(count)); + size += count; + } + } + + //----------------------------------------------------------------------- + /** + * Returns an array of all of this multiset's elements. + * + * @return an array of all of this multiset's elements + */ + @Override + public Object[] toArray() { + final Object[] result = new Object[size()]; + int i = 0; + for (final Map.Entry entry : map.entrySet()) { + final E current = entry.getKey(); + final MutableInteger count = entry.getValue(); + for (int index = count.value; index > 0; index--) { + result[i++] = current; + } + } + return result; + } + + /** + * Returns an array of all of this multiset's elements. + * If the input array has more elements than are in the multiset, + * trailing elements will be set to null. + * + * @param the type of the array elements + * @param array the array to populate + * @return an array of all of this multiset's elements + * @throws ArrayStoreException if the runtime type of the specified array is not + * a supertype of the runtime type of the elements in this list + * @throws NullPointerException if the specified array is null + */ + @Override + public T[] toArray(T[] array) { + final int size = size(); + if (array.length < size) { + @SuppressWarnings("unchecked") // safe as both are of type T + final T[] unchecked = (T[]) Array.newInstance(array.getClass().getComponentType(), size); + array = unchecked; + } + + int i = 0; + for (final Map.Entry entry : map.entrySet()) { + final E current = entry.getKey(); + final MutableInteger count = entry.getValue(); + for (int index = count.value; index > 0; index--) { + // unsafe, will throw ArrayStoreException if types are not compatible, see javadoc + @SuppressWarnings("unchecked") + final T unchecked = (T) current; + array[i++] = unchecked; + } + } + while (i < array.length) { + array[i++] = null; + } + return array; + } + + //----------------------------------------------------------------------- + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (object instanceof MultiSet == false) { + return false; + } + final MultiSet other = (MultiSet) object; + if (other.size() != size()) { + return false; + } + for (final E element : map.keySet()) { + if (other.getCount(element) != getCount(element)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int total = 0; + for (final Map.Entry entry : map.entrySet()) { + final E element = entry.getKey(); + final MutableInteger count = entry.getValue(); + total += (element == null ? 0 : element.hashCode()) ^ count.value; + } + return total; + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMultiSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMultiSet.java new file mode 100644 index 000000000..ddfef0c80 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMultiSet.java @@ -0,0 +1,509 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multiset; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.MultiSet; +import org.apache.commons.collections4.Transformer; + +/** + * Abstract implementation of the {@link MultiSet} interface to simplify the + * creation of subclass implementations. + * + * @since 4.1 + * @version $Id: AbstractMultiSet.java 1715563 2015-11-21 20:13:35Z tn $ + */ +public abstract class AbstractMultiSet extends AbstractCollection implements MultiSet { + + /** View of the elements */ + private transient Set uniqueSet; + /** View of the entries */ + private transient Set> entrySet; + + /** + * Constructor needed for subclass serialisation. + */ + protected AbstractMultiSet() { + super(); + } + + //----------------------------------------------------------------------- + /** + * Returns the number of elements in this multiset. + * + * @return current size of the multiset + */ + @Override + public int size() { + int totalSize = 0; + for (Entry entry : entrySet()) { + totalSize += entry.getCount(); + } + return totalSize; + } + + /** + * Returns the number of occurrence of the given element in this multiset by + * iterating over its entrySet. + * + * @param object the object to search for + * @return the number of occurrences of the object, zero if not found + */ + @Override + public int getCount(final Object object) { + for (Entry entry : entrySet()) { + final E element = entry.getElement(); + if (element == object || + element != null && element.equals(object)) { + return entry.getCount(); + } + } + return 0; + } + + @Override + public int setCount(final E object, final int count) { + if (count < 0) { + throw new IllegalArgumentException("Count must not be negative."); + } + + int oldCount = getCount(object); + if (oldCount < count) { + add(object, count - oldCount); + } else { + remove(object, oldCount - count); + } + return oldCount; + } + + //----------------------------------------------------------------------- + /** + * Determines if the multiset contains the given element. + * + * @param object the object to search for + * @return true if the multiset contains the given element + */ + @Override + public boolean contains(final Object object) { + return getCount(object) > 0; + } + + //----------------------------------------------------------------------- + /** + * Gets an iterator over the multiset elements. Elements present in the + * MultiSet more than once will be returned repeatedly. + * + * @return the iterator + */ + @Override + public Iterator iterator() { + return new MultiSetIterator(this); + } + + /** + * Inner class iterator for the MultiSet. + */ + private static class MultiSetIterator implements Iterator { + private final AbstractMultiSet parent; + private final Iterator> entryIterator; + private Entry current; + private int itemCount; + private boolean canRemove; + + /** + * Constructor. + * + * @param parent the parent multiset + */ + public MultiSetIterator(final AbstractMultiSet parent) { + this.parent = parent; + this.entryIterator = parent.entrySet().iterator(); + this.current = null; + this.canRemove = false; + } + + /** {@inheritDoc} */ + @Override + public boolean hasNext() { + return itemCount > 0 || entryIterator.hasNext(); + } + + /** {@inheritDoc} */ + @Override + public E next() { + if (itemCount == 0) { + current = entryIterator.next(); + itemCount = current.getCount(); + } + canRemove = true; + itemCount--; + return current.getElement(); + } + + /** {@inheritDoc} */ + @Override + public void remove() { + if (canRemove == false) { + throw new IllegalStateException(); + } + final int count = current.getCount(); + if (count > 1) { + parent.remove(current.getElement()); + } else { + entryIterator.remove(); + } + canRemove = false; + } + } + + //----------------------------------------------------------------------- + @Override + public boolean add(final E object) { + add(object, 1); + return true; + } + + @Override + public int add(final E object, final int occurrences) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + /** + * Clears the multiset removing all elements from the entrySet. + */ + @Override + public void clear() { + Iterator> it = entrySet().iterator(); + while (it.hasNext()) { + it.next(); + it.remove(); + } + } + + @Override + public boolean remove(final Object object) { + return remove(object, 1) != 0; + } + + @Override + public int remove(final Object object, final int occurrences) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + boolean result = false; + final Iterator i = coll.iterator(); + while (i.hasNext()) { + final Object obj = i.next(); + final boolean changed = remove(obj, getCount(obj)) != 0; + result = result || changed; + } + return result; + } + + //----------------------------------------------------------------------- + /** + * Returns a view of the unique elements of this multiset. + * + * @return the set of unique elements in this multiset + */ + @Override + public Set uniqueSet() { + if (uniqueSet == null) { + uniqueSet = createUniqueSet(); + } + return uniqueSet; + } + + /** + * Create a new view for the set of unique elements in this multiset. + * + * @return a view of the set of unique elements + */ + protected Set createUniqueSet() { + return new UniqueSet(this); + } + + /** + * Creates a unique set iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the uniqueSet iterator + */ + protected Iterator createUniqueSetIterator() { + final Transformer, E> transformer = new Transformer, E>() { + @Override + public E transform(Entry entry) { + return entry.getElement(); + } + }; + return IteratorUtils.transformedIterator(entrySet().iterator(), transformer); + } + + /** + * Returns an unmodifiable view of the entries of this multiset. + * + * @return the set of entries in this multiset + */ + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = createEntrySet(); + } + return entrySet; + } + + /** + * Create a new view for the set of entries in this multiset. + * + * @return a view of the set of entries + */ + protected Set> createEntrySet() { + return new EntrySet(this); + } + + /** + * Returns the number of unique elements in this multiset. + * + * @return the number of unique elements + */ + protected abstract int uniqueElements(); + + /** + * Creates an entry set iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the entrySet iterator + */ + protected abstract Iterator> createEntrySetIterator(); + + //----------------------------------------------------------------------- + /** + * Inner class UniqueSet. + */ + protected static class UniqueSet extends AbstractSet { + + /** The parent multiset */ + protected final AbstractMultiSet parent; + + /** + * Constructs a new unique element view of the MultiSet. + * + * @param parent the parent MultiSet + */ + protected UniqueSet(final AbstractMultiSet parent) { + this.parent = parent; + } + + @Override + public Iterator iterator() { + return parent.createUniqueSetIterator(); + } + + @Override + public boolean contains(final Object key) { + return parent.contains(key); + } + + @Override + public boolean containsAll(final Collection coll) { + return parent.containsAll(coll); + } + + @Override + public boolean remove(final Object key) { + return parent.remove(key, parent.getCount(key)) != 0; + } + + @Override + public int size() { + return parent.uniqueElements(); + } + + @Override + public void clear() { + parent.clear(); + } + } + + //----------------------------------------------------------------------- + /** + * Inner class EntrySet. + */ + protected static class EntrySet extends AbstractSet> { + + private final AbstractMultiSet parent; + + /** + * Constructs a new view of the MultiSet. + * + * @param parent the parent MultiSet + */ + protected EntrySet(final AbstractMultiSet parent) { + this.parent = parent; + } + + @Override + public int size() { + return parent.uniqueElements(); + } + + @Override + public Iterator> iterator() { + return parent.createEntrySetIterator(); + } + + @Override + public boolean contains(final Object obj) { + if (obj instanceof Entry == false) { + return false; + } + final Entry entry = (Entry) obj; + final Object element = entry.getElement(); + return parent.getCount(element) == entry.getCount(); + } + + @Override + public boolean remove(final Object obj) { + if (obj instanceof Entry == false) { + return false; + } + final Entry entry = (Entry) obj; + final Object element = entry.getElement(); + if (parent.contains(element)) { + final int count = parent.getCount(element); + if (entry.getCount() == count) { + parent.remove(element, count); + return true; + } + } + return false; + } + } + + /** + * Inner class AbstractEntry. + */ + protected static abstract class AbstractEntry implements Entry { + + @Override + public boolean equals(Object object) { + if (object instanceof Entry) { + final Entry other = (Entry) object; + final E element = this.getElement(); + final Object otherElement = other.getElement(); + + return this.getCount() == other.getCount() && + (element == otherElement || + element != null && element.equals(otherElement)); + } + return false; + } + + @Override + public int hashCode() { + final E element = getElement(); + return ((element == null) ? 0 : element.hashCode()) ^ getCount(); + } + + @Override + public String toString() { + return String.format("%s:%d", getElement(), getCount()); + } + + } + + //----------------------------------------------------------------------- + /** + * Write the multiset out using a custom routine. + * @param out the output stream + * @throws IOException any of the usual I/O related exceptions + */ + protected void doWriteObject(final ObjectOutputStream out) throws IOException { + out.writeInt(entrySet().size()); + for (final Entry entry : entrySet()) { + out.writeObject(entry.getElement()); + out.writeInt(entry.getCount()); + } + } + + /** + * Read the multiset in using a custom routine. + * @param in the input stream + * @throws IOException any of the usual I/O related exceptions + * @throws ClassNotFoundException if the stream contains an object which class can not be loaded + * @throws ClassCastException if the stream does not contain the correct objects + */ + protected void doReadObject(final ObjectInputStream in) + throws IOException, ClassNotFoundException { + final int entrySize = in.readInt(); + for (int i = 0; i < entrySize; i++) { + @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect + final E obj = (E) in.readObject(); + final int count = in.readInt(); + setCount(obj, count); + } + } + + //----------------------------------------------------------------------- + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (object instanceof MultiSet == false) { + return false; + } + final MultiSet other = (MultiSet) object; + if (other.size() != size()) { + return false; + } + for (final Entry entry : entrySet()) { + if (other.getCount(entry.getElement()) != getCount(entry.getElement())) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + return entrySet().hashCode(); + } + + /** + * Implement a toString() method suitable for debugging. + * + * @return a debugging toString + */ + @Override + public String toString() { + return entrySet().toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMultiSetDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMultiSetDecorator.java new file mode 100644 index 000000000..8838d16ba --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/AbstractMultiSetDecorator.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multiset; + +import java.util.Set; + +import org.apache.commons.collections4.MultiSet; +import org.apache.commons.collections4.collection.AbstractCollectionDecorator; + +/** + * Decorates another MultSet to provide additional behaviour. + *

              + * Methods are forwarded directly to the decorated multiset. + * + * @since 4.1 + * @version $Id: AbstractMultiSetDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractMultiSetDecorator + extends AbstractCollectionDecorator implements MultiSet { + + /** Serialization version */ + private static final long serialVersionUID = 20150610L; + + /** + * Constructor only used in deserialization, do not use otherwise. + */ + protected AbstractMultiSetDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param multiset the multiset to decorate, must not be null + * @throws NullPointerException if multiset is null + */ + protected AbstractMultiSetDecorator(final MultiSet multiset) { + super(multiset); + } + + /** + * Gets the multiset being decorated. + * + * @return the decorated multiset + */ + @Override + protected MultiSet decorated() { + return (MultiSet) super.decorated(); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + //----------------------------------------------------------------------- + + @Override + public int getCount(final Object object) { + return decorated().getCount(object); + } + + @Override + public int setCount(E object, int count) { + return decorated().setCount(object, count); + } + + @Override + public int add(final E object, final int count) { + return decorated().add(object, count); + } + + @Override + public int remove(final Object object, final int count) { + return decorated().remove(object, count); + } + + @Override + public Set uniqueSet() { + return decorated().uniqueSet(); + } + + @Override + public Set> entrySet() { + return decorated().entrySet(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multiset/HashMultiSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/HashMultiSet.java new file mode 100644 index 000000000..047422313 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/HashMultiSet.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multiset; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; + +/** + * Implements {@code MultiSet}, using a {@link HashMap} to provide the + * data storage. This is the standard implementation of a multiset. + *

              + * A {@code MultiSet} stores each object in the collection together with a + * count of occurrences. Extra methods on the interface allow multiple copies + * of an object to be added or removed at once. + * + * @since 4.1 + * @version $Id: HashMultiSet.java 1715563 2015-11-21 20:13:35Z tn $ + */ +public class HashMultiSet extends AbstractMapMultiSet implements Serializable { + + /** Serial version lock */ + private static final long serialVersionUID = 20150610L; + + /** + * Constructs an empty {@link HashMultiSet}. + */ + public HashMultiSet() { + super(new HashMap()); + } + + /** + * Constructs a multiset containing all the members of the given collection. + * + * @param coll a collection to copy into this multiset + */ + public HashMultiSet(final Collection coll) { + this(); + addAll(coll); + } + + //----------------------------------------------------------------------- + /** + * Write the multiset out using a custom routine. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + super.doWriteObject(out); + } + + /** + * Read the multiset in using a custom routine. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setMap(new HashMap()); + super.doReadObject(in); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multiset/PredicatedMultiSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/PredicatedMultiSet.java new file mode 100644 index 000000000..2518bbbb6 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/PredicatedMultiSet.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multiset; + +import java.util.Set; + +import org.apache.commons.collections4.MultiSet; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.collection.PredicatedCollection; + +/** + * Decorates another {@link MultiSet} to validate that additions + * match a specified predicate. + *

              + * This multiset exists to provide validation for the decorated multiset. + * It is normally created to decorate an empty multiset. + * If an object cannot be added to the multiset, an {@link IllegalArgumentException} + * is thrown. + *

              + * One usage would be to ensure that no null entries are added to the multiset. + *

              + * MultiSet<E> set =
              + *      PredicatedMultiSet.predicatedMultiSet(new HashMultiSet<E>(),
              + *                                            NotNullPredicate.notNullPredicate());
              + * 
              + * + * @since 4.1 + * @version $Id: PredicatedMultiSet.java 1688308 2015-06-29 21:28:54Z tn $ + */ +public class PredicatedMultiSet extends PredicatedCollection implements MultiSet { + + /** Serialization version */ + private static final long serialVersionUID = 20150629L; + + /** + * Factory method to create a predicated (validating) multiset. + *

              + * If there are any elements already in the multiset being decorated, they + * are validated. + * + * @param the type of the elements in the multiset + * @param multiset the multiset to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @return a new predicated MultiSet + * @throws NullPointerException if multiset or predicate is null + * @throws IllegalArgumentException if the multiset contains invalid elements + */ + public static PredicatedMultiSet predicatedMultiSet(final MultiSet multiset, + final Predicate predicate) { + return new PredicatedMultiSet(multiset, predicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the multiset being decorated, they + * are validated. + * + * @param multiset the multiset to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @throws NullPointerException if multiset or predicate is null + * @throws IllegalArgumentException if the multiset contains invalid elements + */ + protected PredicatedMultiSet(final MultiSet multiset, final Predicate predicate) { + super(multiset, predicate); + } + + /** + * Gets the decorated multiset. + * + * @return the decorated multiset + */ + @Override + protected MultiSet decorated() { + return (MultiSet) super.decorated(); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + //----------------------------------------------------------------------- + + @Override + public int add(final E object, final int count) { + validate(object); + return decorated().add(object, count); + } + + @Override + public int remove(final Object object, final int count) { + return decorated().remove(object, count); + } + + @Override + public int getCount(final Object object) { + return decorated().getCount(object); + } + + @Override + public int setCount(E object, int count) { + validate(object); + return decorated().setCount(object, count); + } + + @Override + public Set uniqueSet() { + return decorated().uniqueSet(); + } + + @Override + public Set> entrySet() { + return decorated().entrySet(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multiset/SynchronizedMultiSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/SynchronizedMultiSet.java new file mode 100644 index 000000000..96d6a6765 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/SynchronizedMultiSet.java @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multiset; + +import java.util.Set; + +import org.apache.commons.collections4.MultiSet; +import org.apache.commons.collections4.collection.SynchronizedCollection; + +/** + * Decorates another {@link MultiSet} to synchronize its behaviour + * for a multi-threaded environment. + *

              + * Methods are synchronized, then forwarded to the decorated multiset. + * Iterators must be separately synchronized around the loop. + * + * @since 4.1 + * @version $Id: SynchronizedMultiSet.java 1714424 2015-11-15 10:06:16Z tn $ + */ +public class SynchronizedMultiSet extends SynchronizedCollection implements MultiSet { + + /** Serialization version */ + private static final long serialVersionUID = 20150629L; + + /** + * Factory method to create a synchronized multiset. + * + * @param the type of the elements in the multiset + * @param multiset the multiset to decorate, must not be null + * @return a new synchronized MultiSet + * @throws NullPointerException if multiset is null + */ + public static SynchronizedMultiSet synchronizedMultiSet(final MultiSet multiset) { + return new SynchronizedMultiSet(multiset); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param multiset the multiset to decorate, must not be null + * @throws NullPointerException if multiset is null + */ + protected SynchronizedMultiSet(final MultiSet multiset) { + super(multiset); + } + + /** + * Constructor that wraps (not copies). + * + * @param multiset the multiset to decorate, must not be null + * @param lock the lock to use, must not be null + * @throws NullPointerException if multiset or lock is null + */ + protected SynchronizedMultiSet(final MultiSet multiset, final Object lock) { + super(multiset, lock); + } + + /** + * Gets the multiset being decorated. + * + * @return the decorated multiset + */ + @Override + protected MultiSet decorated() { + return (MultiSet) super.decorated(); + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + synchronized (lock) { + return decorated().equals(object); + } + } + + @Override + public int hashCode() { + synchronized (lock) { + return decorated().hashCode(); + } + } + + //----------------------------------------------------------------------- + + @Override + public int add(final E object, final int count) { + synchronized (lock) { + return decorated().add(object, count); + } + } + + @Override + public int remove(final Object object, final int count) { + synchronized (lock) { + return decorated().remove(object, count); + } + } + + @Override + public int getCount(final Object object) { + synchronized (lock) { + return decorated().getCount(object); + } + } + + @Override + public int setCount(E object, int count) { + synchronized (lock) { + return decorated().setCount(object, count); + } + } + + @Override + public Set uniqueSet() { + synchronized (lock) { + final Set set = decorated().uniqueSet(); + return new SynchronizedSet(set, lock); + } + } + + @Override + public Set> entrySet() { + synchronized (lock) { + final Set> set = decorated().entrySet(); + return new SynchronizedSet>(set, lock); + } + } + + //----------------------------------------------------------------------- + /** + * Synchronized Set for the MultiSet class. + */ + static class SynchronizedSet extends SynchronizedCollection implements Set { + /** Serialization version */ + private static final long serialVersionUID = 20150629L; + + /** + * Constructor. + * @param set the set to decorate + * @param lock the lock to use, shared with the multiset + */ + SynchronizedSet(final Set set, final Object lock) { + super(set, lock); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multiset/UnmodifiableMultiSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/UnmodifiableMultiSet.java new file mode 100644 index 000000000..abb380e8d --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/UnmodifiableMultiSet.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.multiset; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.collections4.MultiSet; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; +import org.apache.commons.collections4.set.UnmodifiableSet; + +/** + * Decorates another {@link MultiSet} to ensure it can't be altered. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 4.1 + * @version $Id: UnmodifiableMultiSet.java 1684982 2015-06-11 22:05:50Z tn $ + */ +public final class UnmodifiableMultiSet + extends AbstractMultiSetDecorator implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = 20150611L; + + /** + * Factory method to create an unmodifiable multiset. + *

              + * If the multiset passed in is already unmodifiable, it is returned. + * + * @param the type of the elements in the multiset + * @param multiset the multiset to decorate, may not be null + * @return an unmodifiable MultiSet + * @throws NullPointerException if multiset is null + */ + public static MultiSet unmodifiableMultiSet(final MultiSet multiset) { + if (multiset instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final MultiSet tmpMultiSet = (MultiSet) multiset; + return tmpMultiSet; + } + return new UnmodifiableMultiSet(multiset); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param multiset the multiset to decorate, may not be null + * @throws NullPointerException if multiset is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableMultiSet(final MultiSet multiset) { + super((MultiSet) multiset); + } + + //----------------------------------------------------------------------- + /** + * Write the collection out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the collection in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @throws ClassCastException if deserialised object has wrong type + */ + @SuppressWarnings("unchecked") // will throw CCE, see Javadoc + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setCollection((Collection) in.readObject()); + } + + //----------------------------------------------------------------------- + @Override + public Iterator iterator() { + return UnmodifiableIterator. unmodifiableIterator(decorated().iterator()); + } + + @Override + public boolean add(final E object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + @Override + public int setCount(E object, int count) { + throw new UnsupportedOperationException(); + } + + @Override + public int add(final E object, final int count) { + throw new UnsupportedOperationException(); + } + + @Override + public int remove(final Object object, final int count) { + throw new UnsupportedOperationException(); + } + + @Override + public Set uniqueSet() { + final Set set = decorated().uniqueSet(); + return UnmodifiableSet.unmodifiableSet(set); + } + + @Override + public Set> entrySet() { + final Set> set = decorated().entrySet(); + return UnmodifiableSet.unmodifiableSet(set); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/multiset/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/package-info.java new file mode 100644 index 000000000..49c07f7b6 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/multiset/package-info.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the + * {@link org.apache.commons.collections4.MultiSet MultiSet} and + * {@link org.apache.commons.collections4.SortedMultiSet SortedMultiSet} interfaces. + * A multiset stores an object and a count of the number of occurrences of the object. + *

              + * The following implementations are provided in the package: + *

                + *
              • HashMultiSet - implementation that uses a HashMap to store the data + *
              • TreeMultiSet - implementation that uses a TreeMap to store the data + *
              + *

              + * The following decorators are provided in the package: + *

                + *
              • Predicated - ensures that only elements that are valid according to a predicate can be added + *
              • Synchronized - synchronizes method access for multi-threaded environments + *
              • Unmodifiable - ensures the multiset cannot be altered + *
              + * + * @version $Id: package-info.java 1688308 2015-06-29 21:28:54Z tn $ + */ +package org.apache.commons.collections4.multiset; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/overview.html b/fine-commons-collections4/src/org/apache/commons/collections4/overview.html new file mode 100644 index 000000000..9a664e7e1 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/overview.html @@ -0,0 +1,113 @@ + + + +

              + Commons-Collections contains implementations, enhancements and utilities + that complement the Java Collections Framework. +

              +

              + The Apache Commons Collections Framework component adds a significant + amount of enhancements to the standard JDK collections. These enhancements + come in the form of new interfaces, new implementations and utility classes. +

              +

              + See also the java.util package for the standard Java collections. +

              + +

              Main features

              +

              + Commons-Collections defines a number of key interfaces: +

              + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
              InterfaceDescription
              + {@link org.apache.commons.collections.Bag} + + A new Collection subinterface that stores each object together + with the number of occurrences. Methods are provided to get the number of + occurrences, and to add and remove a certain number of that object. +
              + {@link org.apache.commons.collections.Buffer} + + A new Collection subinterface that allows objects to be removed + in some well-defined order. Methods enable the next item to be peeked and removed. +
              + {@link org.apache.commons.collections.BidiMap} + + A new Map subinterface that allows lookup from key to value and + from value to key with equal ease. +
              + {@link org.apache.commons.collections.OrderedMap} + + A new Map subinterface that is used when a map has an order, but is + not sorted. Methods enable bidirectional iteration through the map. +
              + {@link org.apache.commons.collections.MapIterator} + + A new Iterator subinterface specially designed for maps. This iterator + avoids the need for entrySet iteration of a map, and is simpler to use. +
              + {@link org.apache.commons.collections.ResettableIterator} + + A new Iterator subinterface that allows the iteration to be reset back + to the start. Many iterators in this library have this functionality. +
              + {@link org.apache.commons.collections.Closure}
              + {@link org.apache.commons.collections.Predicate}
              + {@link org.apache.commons.collections.Transformer}
              + {@link org.apache.commons.collections.Factory}
              +
              + A group of functor interfaces that provide plugin behaviour to various + collections and utilities. +
              +

              + In addition to the interfaces, there are many implementations. + Consult each subpackage for full details of these. +

              + + diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/package-info.java new file mode 100644 index 000000000..7e44dc4c5 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/package-info.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains the interfaces and utilities shared across all the subpackages of this component. + *

              + * The following collection implementations are provided in the package: + *

                + *
              • ArrayStack - a non synchronized Stack that follows the same API as {@code java.util Stack} + *
              + * + * @version $Id: package-info.java 1469004 2013-04-17 17:37:03Z tn $ + */ +package org.apache.commons.collections4; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/queue/AbstractQueueDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/queue/AbstractQueueDecorator.java new file mode 100644 index 000000000..862383246 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/queue/AbstractQueueDecorator.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.queue; + +import java.util.Queue; + +import org.apache.commons.collections4.collection.AbstractCollectionDecorator; + +/** + * Decorates another {@link Queue} to provide additional behaviour. + *

              + * Methods are forwarded directly to the decorated queue. + *

              + * This implementation does not forward the hashCode and equals methods through + * to the backing object, but relies on Object's implementation. This is + * necessary as some Queue implementations, e.g. LinkedList, have custom a + * equals implementation for which symmetry can not be preserved. + * See class javadoc of AbstractCollectionDecorator for more information. + * + * @param the type of the elements in the queue + * @since 4.0 + * @version $Id: AbstractQueueDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractQueueDecorator extends AbstractCollectionDecorator + implements Queue { + + /** Serialization version */ + private static final long serialVersionUID = -2629815475789577029L; + + /** + * Constructor only used in deserialization, do not use otherwise. + */ + protected AbstractQueueDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param queue the queue to decorate, must not be null + * @throws NullPointerException if queue is null + */ + protected AbstractQueueDecorator(final Queue queue) { + super(queue); + } + + /** + * Gets the queue being decorated. + * + * @return the decorated queue + */ + @Override + protected Queue decorated() { + return (Queue) super.decorated(); + } + + //----------------------------------------------------------------------- + + public boolean offer(final E obj) { + return decorated().offer(obj); + } + + public E poll() { + return decorated().poll(); + } + + public E peek() { + return decorated().peek(); + } + + public E element() { + return decorated().element(); + } + + public E remove() { + return decorated().remove(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/queue/CircularFifoQueue.java b/fine-commons-collections4/src/org/apache/commons/collections4/queue/CircularFifoQueue.java new file mode 100644 index 000000000..5fd8ba957 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/queue/CircularFifoQueue.java @@ -0,0 +1,420 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.queue; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; + +import org.apache.commons.collections4.BoundedCollection; + +/** + * CircularFifoQueue is a first-in first-out queue with a fixed size that + * replaces its oldest element if full. + *

              + * The removal order of a {@link CircularFifoQueue} is based on the + * insertion order; elements are removed in the same order in which they + * were added. The iteration order is the same as the removal order. + *

              + * The {@link #add(Object)}, {@link #remove()}, {@link #peek()}, {@link #poll}, + * {@link #offer(Object)} operations all perform in constant time. + * All other operations perform in linear time or worse. + *

              + * This queue prevents null objects from being added. + * + * @since 4.0 + * @version $Id: CircularFifoQueue.java 1648957 2015-01-01 22:01:31Z tn $ + */ +public class CircularFifoQueue extends AbstractCollection + implements Queue, BoundedCollection, Serializable { + + /** Serialization version. */ + private static final long serialVersionUID = -8423413834657610406L; + + /** Underlying storage array. */ + private transient E[] elements; + + /** Array index of first (oldest) queue element. */ + private transient int start = 0; + + /** + * Index mod maxElements of the array position following the last queue + * element. Queue elements start at elements[start] and "wrap around" + * elements[maxElements-1], ending at elements[decrement(end)]. + * For example, elements = {c,a,b}, start=1, end=1 corresponds to + * the queue [a,b,c]. + */ + private transient int end = 0; + + /** Flag to indicate if the queue is currently full. */ + private transient boolean full = false; + + /** Capacity of the queue. */ + private final int maxElements; + + /** + * Constructor that creates a queue with the default size of 32. + */ + public CircularFifoQueue() { + this(32); + } + + /** + * Constructor that creates a queue with the specified size. + * + * @param size the size of the queue (cannot be changed) + * @throws IllegalArgumentException if the size is < 1 + */ + @SuppressWarnings("unchecked") + public CircularFifoQueue(final int size) { + if (size <= 0) { + throw new IllegalArgumentException("The size must be greater than 0"); + } + elements = (E[]) new Object[size]; + maxElements = elements.length; + } + + /** + * Constructor that creates a queue from the specified collection. + * The collection size also sets the queue size. + * + * @param coll the collection to copy into the queue, may not be null + * @throws NullPointerException if the collection is null + */ + public CircularFifoQueue(final Collection coll) { + this(coll.size()); + addAll(coll); + } + + //----------------------------------------------------------------------- + /** + * Write the queue out using a custom routine. + * + * @param out the output stream + * @throws IOException if an I/O error occurs while writing to the output stream + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeInt(size()); + for (final E e : this) { + out.writeObject(e); + } + } + + /** + * Read the queue in using a custom routine. + * + * @param in the input stream + * @throws IOException if an I/O error occurs while writing to the output stream + * @throws ClassNotFoundException if the class of a serialized object can not be found + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + elements = (E[]) new Object[maxElements]; + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + elements[i] = (E) in.readObject(); + } + start = 0; + full = size == maxElements; + if (full) { + end = 0; + } else { + end = size; + } + } + + //----------------------------------------------------------------------- + /** + * Returns the number of elements stored in the queue. + * + * @return this queue's size + */ + @Override + public int size() { + int size = 0; + + if (end < start) { + size = maxElements - start + end; + } else if (end == start) { + size = full ? maxElements : 0; + } else { + size = end - start; + } + + return size; + } + + /** + * Returns true if this queue is empty; false otherwise. + * + * @return true if this queue is empty + */ + @Override + public boolean isEmpty() { + return size() == 0; + } + + /** + * {@inheritDoc} + *

              + * A {@code CircularFifoQueue} can never be full, thus this returns always + * {@code false}. + * + * @return always returns {@code false} + */ + public boolean isFull() { + return false; + } + + /** + * Returns {@code true} if the capacity limit of this queue has been reached, + * i.e. the number of elements stored in the queue equals its maximum size. + * + * @return {@code true} if the capacity limit has been reached, {@code false} otherwise + * @since 4.1 + */ + public boolean isAtFullCapacity() { + return size() == maxElements; + } + + /** + * Gets the maximum size of the collection (the bound). + * + * @return the maximum number of elements the collection can hold + */ + public int maxSize() { + return maxElements; + } + + /** + * Clears this queue. + */ + @Override + public void clear() { + full = false; + start = 0; + end = 0; + Arrays.fill(elements, null); + } + + /** + * Adds the given element to this queue. If the queue is full, the least recently added + * element is discarded so that a new element can be inserted. + * + * @param element the element to add + * @return true, always + * @throws NullPointerException if the given element is null + */ + @Override + public boolean add(final E element) { + if (null == element) { + throw new NullPointerException("Attempted to add null object to queue"); + } + + if (isAtFullCapacity()) { + remove(); + } + + elements[end++] = element; + + if (end >= maxElements) { + end = 0; + } + + if (end == start) { + full = true; + } + + return true; + } + + /** + * Returns the element at the specified position in this queue. + * + * @param index the position of the element in the queue + * @return the element at position {@code index} + * @throws NoSuchElementException if the requested position is outside the range [0, size) + */ + public E get(final int index) { + final int sz = size(); + if (index < 0 || index >= sz) { + throw new NoSuchElementException( + String.format("The specified index (%1$d) is outside the available range [0, %2$d)", + Integer.valueOf(index), Integer.valueOf(sz))); + } + + final int idx = (start + index) % maxElements; + return elements[idx]; + } + + //----------------------------------------------------------------------- + + /** + * Adds the given element to this queue. If the queue is full, the least recently added + * element is discarded so that a new element can be inserted. + * + * @param element the element to add + * @return true, always + * @throws NullPointerException if the given element is null + */ + public boolean offer(E element) { + return add(element); + } + + public E poll() { + if (isEmpty()) { + return null; + } + return remove(); + } + + public E element() { + if (isEmpty()) { + throw new NoSuchElementException("queue is empty"); + } + return peek(); + } + + public E peek() { + if (isEmpty()) { + return null; + } + return elements[start]; + } + + public E remove() { + if (isEmpty()) { + throw new NoSuchElementException("queue is empty"); + } + + final E element = elements[start]; + if (null != element) { + elements[start++] = null; + + if (start >= maxElements) { + start = 0; + } + full = false; + } + return element; + } + + //----------------------------------------------------------------------- + /** + * Increments the internal index. + * + * @param index the index to increment + * @return the updated index + */ + private int increment(int index) { + index++; + if (index >= maxElements) { + index = 0; + } + return index; + } + + /** + * Decrements the internal index. + * + * @param index the index to decrement + * @return the updated index + */ + private int decrement(int index) { + index--; + if (index < 0) { + index = maxElements - 1; + } + return index; + } + + /** + * Returns an iterator over this queue's elements. + * + * @return an iterator over this queue's elements + */ + @Override + public Iterator iterator() { + return new Iterator() { + + private int index = start; + private int lastReturnedIndex = -1; + private boolean isFirst = full; + + public boolean hasNext() { + return isFirst || index != end; + } + + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + isFirst = false; + lastReturnedIndex = index; + index = increment(index); + return elements[lastReturnedIndex]; + } + + public void remove() { + if (lastReturnedIndex == -1) { + throw new IllegalStateException(); + } + + // First element can be removed quickly + if (lastReturnedIndex == start) { + CircularFifoQueue.this.remove(); + lastReturnedIndex = -1; + return; + } + + int pos = lastReturnedIndex + 1; + if (start < lastReturnedIndex && pos < end) { + // shift in one part + System.arraycopy(elements, pos, elements, lastReturnedIndex, end - pos); + } else { + // Other elements require us to shift the subsequent elements + while (pos != end) { + if (pos >= maxElements) { + elements[pos - 1] = elements[0]; + pos = 0; + } else { + elements[decrement(pos)] = elements[pos]; + pos = increment(pos); + } + } + } + + lastReturnedIndex = -1; + end = decrement(end); + elements[end] = null; + full = false; + index = decrement(index); + } + + }; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/queue/PredicatedQueue.java b/fine-commons-collections4/src/org/apache/commons/collections4/queue/PredicatedQueue.java new file mode 100644 index 000000000..ffbe128df --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/queue/PredicatedQueue.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.queue; + +import java.util.Queue; + +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.collection.PredicatedCollection; + +/** + * Decorates another {@link Queue} to validate that additions + * match a specified predicate. + *

              + * This queue exists to provide validation for the decorated queue. + * It is normally created to decorate an empty queue. + * If an object cannot be added to the queue, an IllegalArgumentException is thrown. + *

              + * One usage would be to ensure that no null entries are added to the queue. + *

              Queue queue = PredicatedQueue.predicatedQueue(new UnboundedFifoQueue(), NotNullPredicate.INSTANCE);
              + * + * @since 4.0 + * @version $Id: PredicatedQueue.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PredicatedQueue extends PredicatedCollection implements Queue { + + /** Serialization version */ + private static final long serialVersionUID = 2307609000539943581L; + + /** + * Factory method to create a predicated (validating) queue. + *

              + * If there are any elements already in the queue being decorated, they + * are validated. + * + * @param the type of the elements in the queue + * @param Queue the queue to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @return a new predicated queue + * @throws NullPointerException if queue or predicate is null + * @throws IllegalArgumentException if the queue contains invalid elements + */ + public static PredicatedQueue predicatedQueue(final Queue Queue, + final Predicate predicate) { + return new PredicatedQueue(Queue, predicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the collection being decorated, they + * are validated. + * + * @param queue the queue to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @throws NullPointerException if queue or predicate is null + * @throws IllegalArgumentException if the Queue contains invalid elements + */ + protected PredicatedQueue(final Queue queue, final Predicate predicate) { + super(queue, predicate); + } + + /** + * Gets the queue being decorated. + * + * @return the decorated queue + */ + @Override + protected Queue decorated() { + return (Queue) super.decorated(); + } + + //----------------------------------------------------------------------- + + /** + * Override to validate the object being added to ensure it matches + * the predicate. + * + * @param object the object being added + * @return the result of adding to the underlying queue + * @throws IllegalArgumentException if the add is invalid + */ + public boolean offer(final E object) { + validate(object); + return decorated().offer(object); + } + + public E poll() { + return decorated().poll(); + } + + public E peek() { + return decorated().peek(); + } + + public E element() { + return decorated().element(); + } + + public E remove() { + return decorated().remove(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/queue/TransformedQueue.java b/fine-commons-collections4/src/org/apache/commons/collections4/queue/TransformedQueue.java new file mode 100644 index 000000000..c217ad565 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/queue/TransformedQueue.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.queue; + +import java.util.Queue; + +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.collection.TransformedCollection; + +/** + * Decorates another {@link Queue} to transform objects that are added. + *

              + * The add/offer methods are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must + * use the Integer form to remove objects. + * + * @since 4.0 + * @version $Id: TransformedQueue.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TransformedQueue extends TransformedCollection implements Queue { + + /** Serialization version */ + private static final long serialVersionUID = -7901091318986132033L; + + /** + * Factory method to create a transforming queue. + *

              + * If there are any elements already in the queue being decorated, they + * are NOT transformed. + * Contrast this with {@link #transformedQueue(Queue, Transformer)}. + * + * @param the type of the elements in the queue + * @param queue the queue to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed Queue + * @throws NullPointerException if queue or transformer is null + */ + public static TransformedQueue transformingQueue(final Queue queue, + final Transformer transformer) { + return new TransformedQueue(queue, transformer); + } + + /** + * Factory method to create a transforming queue that will transform + * existing contents of the specified queue. + *

              + * If there are any elements already in the queue being decorated, they + * will be transformed by this method. + * Contrast this with {@link #transformingQueue(Queue, Transformer)}. + * + * @param the type of the elements in the queue + * @param queue the queue to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed Queue + * @throws NullPointerException if queue or transformer is null + * @since 4.0 + */ + public static TransformedQueue transformedQueue(final Queue queue, + final Transformer transformer) { + // throws IAE if queue or transformer is null + final TransformedQueue decorated = new TransformedQueue(queue, transformer); + if (queue.size() > 0) { + @SuppressWarnings("unchecked") // queue is type + final E[] values = (E[]) queue.toArray(); // NOPMD - false positive for generics + queue.clear(); + for (final E value : values) { + decorated.decorated().add(transformer.transform(value)); + } + } + return decorated; + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the queue being decorated, they + * are NOT transformed. + * + * @param queue the queue to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @throws NullPointerException if queue or transformer is null + */ + protected TransformedQueue(final Queue queue, final Transformer transformer) { + super(queue, transformer); + } + + /** + * Gets the decorated queue. + * + * @return the decorated queue + */ + protected Queue getQueue() { + return (Queue) decorated(); + } + + //----------------------------------------------------------------------- + + public boolean offer(final E obj) { + return getQueue().offer(transform(obj)); + } + + public E poll() { + return getQueue().poll(); + } + + public E peek() { + return getQueue().peek(); + } + + public E element() { + return getQueue().element(); + } + + public E remove() { + return getQueue().remove(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/queue/UnmodifiableQueue.java b/fine-commons-collections4/src/org/apache/commons/collections4/queue/UnmodifiableQueue.java new file mode 100644 index 000000000..659099f5a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/queue/UnmodifiableQueue.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.queue; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.Queue; + +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; + +/** + * Decorates another {@link Queue} to ensure it can't be altered. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 4.0 + * @version $Id: UnmodifiableQueue.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableQueue + extends AbstractQueueDecorator + implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = 1832948656215393357L; + + /** + * Factory method to create an unmodifiable queue. + *

              + * If the queue passed in is already unmodifiable, it is returned. + * + * @param the type of the elements in the queue + * @param queue the queue to decorate, must not be null + * @return an unmodifiable Queue + * @throws NullPointerException if queue is null + */ + public static Queue unmodifiableQueue(final Queue queue) { + if (queue instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final Queue tmpQueue = (Queue) queue; + return tmpQueue; + } + return new UnmodifiableQueue(queue); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param queue the queue to decorate, must not be null + * @throws NullPointerException if queue is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableQueue(final Queue queue) { + super((Queue) queue); + } + + //----------------------------------------------------------------------- + /** + * Write the collection out using a custom routine. + * + * @param out the output stream + * @throws IOException if an I/O error occurs while writing to the output stream + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the collection in using a custom routine. + * + * @param in the input stream + * @throws IOException if an I/O error occurs while reading from the input stream + * @throws ClassNotFoundException if the class of a serialized object can not be found + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setCollection((Collection) in.readObject()); + } + + //----------------------------------------------------------------------- + @Override + public Iterator iterator() { + return UnmodifiableIterator.unmodifiableIterator(decorated().iterator()); + } + + @Override + public boolean add(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + + @Override + public boolean offer(final E obj) { + throw new UnsupportedOperationException(); + } + + @Override + public E poll() { + throw new UnsupportedOperationException(); + } + + @Override + public E remove() { + throw new UnsupportedOperationException(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/queue/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/queue/package-info.java new file mode 100644 index 000000000..817932611 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/queue/package-info.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations for the {@link java.util.Queue Queue} interface. + *

              + * The following implementations are provided in the package: + *

                + *
              • CircularFifoQueue - implements a queue with a fixed size that discards oldest when full + *
              + *

              + * The following decorators are provided in the package: + *

                + *
              • Predicated - ensures that only elements that are valid according to a predicate can be added + *
              • Transformed - transforms elements added to the queue + *
              • Unmodifiable - ensures the collection cannot be altered + *
              + * + * @version $Id: package-info.java 1477765 2013-04-30 18:37:37Z tn $ + */ +package org.apache.commons.collections4.queue; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/sequence/CommandVisitor.java b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/CommandVisitor.java new file mode 100644 index 000000000..0b5e612d2 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/CommandVisitor.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +/** + * This interface should be implemented by user object to walk + * through {@link EditScript EditScript} objects. + *

              + * Users should implement this interface in order to walk through + * the {@link EditScript EditScript} object created by the comparison + * of two sequences. This is a direct application of the visitor + * design pattern. The {@link EditScript#visit EditScript.visit} + * method takes an object implementing this interface as an argument, + * it will perform the loop over all commands in the script and the + * proper methods of the user class will be called as the commands are + * encountered. + *

              + * The implementation of the user visitor class will depend on the + * need. Here are two examples. + *

              + * The first example is a visitor that build the longest common + * subsequence: + *

              + * import org.apache.commons.collections4.comparators.sequence.CommandVisitor;
              + *
              + * import java.util.ArrayList;
              + *
              + * public class LongestCommonSubSequence implements CommandVisitor {
              + *
              + *   public LongestCommonSubSequence() {
              + *     a = new ArrayList();
              + *   }
              + *
              + *   public void visitInsertCommand(Object object) {
              + *   }
              + *
              + *   public void visitKeepCommand(Object object) {
              + *     a.add(object);
              + *   }
              + *
              + *   public void visitDeleteCommand(Object object) {
              + *   }
              + *
              + *   public Object[] getSubSequence() {
              + *     return a.toArray();
              + *   }
              + *
              + *   private ArrayList a;
              + *
              + * }
              + * 
              + *

              + * The second example is a visitor that shows the commands and the way + * they transform the first sequence into the second one: + *

              + * import org.apache.commons.collections4.comparators.sequence.CommandVisitor;
              + *
              + * import java.util.Arrays;
              + * import java.util.ArrayList;
              + * import java.util.Iterator;
              + *
              + * public class ShowVisitor implements CommandVisitor {
              + *
              + *   public ShowVisitor(Object[] sequence1) {
              + *     v = new ArrayList();
              + *     v.addAll(Arrays.asList(sequence1));
              + *     index = 0;
              + *   }
              + *
              + *   public void visitInsertCommand(Object object) {
              + *     v.insertElementAt(object, index++);
              + *     display("insert", object);
              + *   }
              + *
              + *   public void visitKeepCommand(Object object) {
              + *     ++index;
              + *     display("keep  ", object);
              + *   }
              + *
              + *   public void visitDeleteCommand(Object object) {
              + *     v.remove(index);
              + *     display("delete", object);
              + *   }
              + *
              + *   private void display(String commandName, Object object) {
              + *     System.out.println(commandName + " " + object + " ->" + this);
              + *   }
              + *
              + *   public String toString() {
              + *     StringBuffer buffer = new StringBuffer();
              + *     for (Iterator iter = v.iterator(); iter.hasNext();) {
              + *       buffer.append(' ').append(iter.next());
              + *     }
              + *     return buffer.toString();
              + *   }
              + *
              + *   private ArrayList v;
              + *   private int index;
              + *
              + * }
              + * 
              + * + * @since 4.0 + * @version $Id: CommandVisitor.java 1477760 2013-04-30 18:34:03Z tn $ + */ +public interface CommandVisitor { + + /** + * Method called when an insert command is encountered. + * + * @param object object to insert (this object comes from the second sequence) + */ + void visitInsertCommand(T object); + + /** + * Method called when a keep command is encountered. + * + * @param object object to keep (this object comes from the first sequence) + */ + void visitKeepCommand(T object); + + /** + * Method called when a delete command is encountered. + * + * @param object object to delete (this object comes from the first sequence) + */ + void visitDeleteCommand(T object); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/sequence/DeleteCommand.java b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/DeleteCommand.java new file mode 100644 index 000000000..1d0cc939f --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/DeleteCommand.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +/** + * Command representing the deletion of one object of the first sequence. + *

              + * When one object of the first sequence has no corresponding object in the + * second sequence at the right place, the {@link EditScript edit script} + * transforming the first sequence into the second sequence uses an instance of + * this class to represent the deletion of this object. The objects embedded in + * these type of commands always come from the first sequence. + * + * @see SequencesComparator + * @see EditScript + * + * @since 4.0 + * @version $Id: DeleteCommand.java 1477760 2013-04-30 18:34:03Z tn $ + */ +public class DeleteCommand extends EditCommand { + + /** + * Simple constructor. Creates a new instance of {@link DeleteCommand}. + * + * @param object the object of the first sequence that should be deleted + */ + public DeleteCommand(final T object) { + super(object); + } + + /** + * Accept a visitor. When a DeleteCommand accepts a visitor, it calls + * its {@link CommandVisitor#visitDeleteCommand visitDeleteCommand} method. + * + * @param visitor the visitor to be accepted + */ + @Override + public void accept(final CommandVisitor visitor) { + visitor.visitDeleteCommand(getObject()); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/sequence/EditCommand.java b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/EditCommand.java new file mode 100644 index 000000000..f8c7ec919 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/EditCommand.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +/** + * Abstract base class for all commands used to transform an objects sequence + * into another one. + *

              + * When two objects sequences are compared through the + * {@link SequencesComparator#getScript SequencesComparator.getScript} method, + * the result is provided has a {@link EditScript script} containing the commands + * that progressively transform the first sequence into the second one. + *

              + * There are only three types of commands, all of which are subclasses of this + * abstract class. Each command is associated with one object belonging to at + * least one of the sequences. These commands are {@link InsertCommand + * InsertCommand} which correspond to an object of the second sequence being + * inserted into the first sequence, {@link DeleteCommand DeleteCommand} which + * correspond to an object of the first sequence being removed and + * {@link KeepCommand KeepCommand} which correspond to an object of the first + * sequence which equals an object in the second sequence. It is + * guaranteed that comparison is always performed this way (i.e. the + * equals method of the object from the first sequence is used and + * the object passed as an argument comes from the second sequence) ; this can + * be important if subclassing is used for some elements in the first sequence + * and the equals method is specialized. + * + * @see SequencesComparator + * @see EditScript + * + * @since 4.0 + * @version $Id: EditCommand.java 1477760 2013-04-30 18:34:03Z tn $ + */ +public abstract class EditCommand { + + /** Object on which the command should be applied. */ + private final T object; + + /** + * Simple constructor. Creates a new instance of EditCommand + * + * @param object reference to the object associated with this command, this + * refers to an element of one of the sequences being compared + */ + protected EditCommand(final T object) { + this.object = object; + } + + /** + * Returns the object associated with this command. + * + * @return the object on which the command is applied + */ + protected T getObject() { + return object; + } + + /** + * Accept a visitor. + *

              + * This method is invoked for each commands belonging to + * an {@link EditScript EditScript}, in order to implement the visitor design pattern + * + * @param visitor the visitor to be accepted + */ + public abstract void accept(CommandVisitor visitor); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/sequence/EditScript.java b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/EditScript.java new file mode 100644 index 000000000..d2e9d5fec --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/EditScript.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class gathers all the {@link EditCommand commands} needed to transform + * one objects sequence into another objects sequence. + *

              + * An edit script is the most general view of the differences between two + * sequences. It is built as the result of the comparison between two sequences + * by the {@link SequencesComparator SequencesComparator} class. The user can + * walk through it using the visitor design pattern. + *

              + * It is guaranteed that the objects embedded in the {@link InsertCommand insert + * commands} come from the second sequence and that the objects embedded in + * either the {@link DeleteCommand delete commands} or {@link KeepCommand keep + * commands} come from the first sequence. This can be important if subclassing + * is used for some elements in the first sequence and the equals + * method is specialized. + * + * @see SequencesComparator + * @see EditCommand + * @see CommandVisitor + * @see ReplacementsHandler + * + * @since 4.0 + * @version $Id: EditScript.java 1477760 2013-04-30 18:34:03Z tn $ + */ +public class EditScript { + + /** Container for the commands. */ + private final List> commands; + + /** Length of the longest common subsequence. */ + private int lcsLength; + + /** Number of modifications. */ + private int modifications; + + /** + * Simple constructor. Creates a new empty script. + */ + public EditScript() { + commands = new ArrayList>(); + lcsLength = 0; + modifications = 0; + } + + /** + * Add a keep command to the script. + * + * @param command command to add + */ + public void append(final KeepCommand command) { + commands.add(command); + ++lcsLength; + } + + /** + * Add an insert command to the script. + * + * @param command command to add + */ + public void append(final InsertCommand command) { + commands.add(command); + ++modifications; + } + + /** + * Add a delete command to the script. + * + * @param command command to add + */ + public void append(final DeleteCommand command) { + commands.add(command); + ++modifications; + } + + /** + * Visit the script. The script implements the visitor design + * pattern, this method is the entry point to which the user supplies its + * own visitor, the script will be responsible to drive it through the + * commands in order and call the appropriate method as each command is + * encountered. + * + * @param visitor the visitor that will visit all commands in turn + */ + public void visit(final CommandVisitor visitor) { + for (final EditCommand command : commands) { + command.accept(visitor); + } + } + + /** + * Get the length of the Longest Common Subsequence (LCS). The length of the + * longest common subsequence is the number of {@link KeepCommand keep + * commands} in the script. + * + * @return length of the Longest Common Subsequence + */ + public int getLCSLength() { + return lcsLength; + } + + /** + * Get the number of effective modifications. The number of effective + * modification is the number of {@link DeleteCommand delete} and + * {@link InsertCommand insert} commands in the script. + * + * @return number of effective modifications + */ + public int getModifications() { + return modifications; + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/sequence/InsertCommand.java b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/InsertCommand.java new file mode 100644 index 000000000..47a913f0a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/InsertCommand.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +/** + * Command representing the insertion of one object of the second sequence. + *

              + * When one object of the second sequence has no corresponding object in the + * first sequence at the right place, the {@link EditScript edit script} + * transforming the first sequence into the second sequence uses an instance of + * this class to represent the insertion of this object. The objects embedded in + * these type of commands always come from the second sequence. + * + * @see SequencesComparator + * @see EditScript + * + * @since 4.0 + * @version $Id: InsertCommand.java 1477760 2013-04-30 18:34:03Z tn $ + */ +public class InsertCommand extends EditCommand { + + /** + * Simple constructor. Creates a new instance of InsertCommand + * + * @param object the object of the second sequence that should be inserted + */ + public InsertCommand(final T object) { + super(object); + } + + /** + * Accept a visitor. When an InsertCommand accepts a visitor, + * it calls its {@link CommandVisitor#visitInsertCommand visitInsertCommand} + * method. + * + * @param visitor the visitor to be accepted + */ + @Override + public void accept(final CommandVisitor visitor) { + visitor.visitInsertCommand(getObject()); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/sequence/KeepCommand.java b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/KeepCommand.java new file mode 100644 index 000000000..a0a39a198 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/KeepCommand.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +/** + * Command representing the keeping of one object present in both sequences. + *

              + * When one object of the first sequence equals another objects in + * the second sequence at the right place, the {@link EditScript edit script} + * transforming the first sequence into the second sequence uses an instance of + * this class to represent the keeping of this object. The objects embedded in + * these type of commands always come from the first sequence. + * + * @see SequencesComparator + * @see EditScript + * + * @since 4.0 + * @version $Id: KeepCommand.java 1477760 2013-04-30 18:34:03Z tn $ + */ +public class KeepCommand extends EditCommand { + + /** + * Simple constructor. Creates a new instance of KeepCommand + * + * @param object the object belonging to both sequences (the object is a + * reference to the instance in the first sequence which is known + * to be equal to an instance in the second sequence) + */ + public KeepCommand(final T object) { + super(object); + } + + /** + * Accept a visitor. When a KeepCommand accepts a visitor, it + * calls its {@link CommandVisitor#visitKeepCommand visitKeepCommand} method. + * + * @param visitor the visitor to be accepted + */ + @Override + public void accept(final CommandVisitor visitor) { + visitor.visitKeepCommand(getObject()); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/sequence/ReplacementsFinder.java b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/ReplacementsFinder.java new file mode 100644 index 000000000..2f9949c76 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/ReplacementsFinder.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class handles sequences of replacements resulting from a comparison. + *

              + * The comparison of two objects sequences leads to the identification of common + * parts and parts which only belong to the first or to the second sequence. The + * common parts appear in the edit script in the form of keep commands, + * they can be considered as synchronization objects between the two sequences. + * These synchronization objects split the two sequences in synchronized + * sub-sequences. The first sequence can be transformed into the second one by + * replacing each synchronized sub-sequence of the first sequence by the + * corresponding sub-sequence of the second sequence. This is a synthetic way to + * see an {@link EditScript edit script}, replacing individual + * {@link DeleteCommand delete}, {@link KeepCommand keep} and + * {@link InsertCommand insert} commands by fewer replacements acting on + * complete sub-sequences. + *

              + * This class is devoted to perform this interpretation. It visits an + * {@link EditScript edit script} (because it implements the + * {@link CommandVisitor CommandVisitor} interface) and calls a user-supplied + * handler implementing the {@link ReplacementsHandler ReplacementsHandler} + * interface to process the sub-sequences. + * + * @see ReplacementsHandler + * @see EditScript + * @see SequencesComparator + * + * @since 4.0 + * @version $Id: ReplacementsFinder.java 1477760 2013-04-30 18:34:03Z tn $ + */ +public class ReplacementsFinder implements CommandVisitor { + + private final List pendingInsertions; + private final List pendingDeletions; + private int skipped; + + /** Handler to call when synchronized sequences are found. */ + private final ReplacementsHandler handler; + + /** + * Simple constructor. Creates a new instance of {@link ReplacementsFinder}. + * + * @param handler handler to call when synchronized sequences are found + */ + public ReplacementsFinder(final ReplacementsHandler handler) { + pendingInsertions = new ArrayList(); + pendingDeletions = new ArrayList(); + skipped = 0; + this.handler = handler; + } + + /** + * Add an object to the pending insertions set. + * + * @param object object to insert + */ + public void visitInsertCommand(final T object) { + pendingInsertions.add(object); + } + + /** + * Handle a synchronization object. + *

              + * When a synchronization object is identified, the pending insertions and + * pending deletions sets are provided to the user handler as subsequences. + * + * @param object synchronization object detected + */ + public void visitKeepCommand(final T object) { + if (pendingDeletions.isEmpty() && pendingInsertions.isEmpty()) { + ++skipped; + } else { + handler.handleReplacement(skipped, pendingDeletions, pendingInsertions); + pendingDeletions.clear(); + pendingInsertions.clear(); + skipped = 1; + } + } + + /** + * Add an object to the pending deletions set. + * + * @param object object to delete + */ + public void visitDeleteCommand(final T object) { + pendingDeletions.add(object); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/sequence/ReplacementsHandler.java b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/ReplacementsHandler.java new file mode 100644 index 000000000..f584a6b42 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/ReplacementsHandler.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +import java.util.List; + +/** + * This interface is devoted to handle synchronized replacement sequences. + * + * @see ReplacementsFinder + * @since 4.0 + * @version $Id: ReplacementsHandler.java 1543277 2013-11-19 00:53:50Z ggregory $ + */ +public interface ReplacementsHandler { + + /** + * Handle two synchronized sequences. + *

              + * This method is called by a {@link ReplacementsFinder ReplacementsFinder} + * instance when it has synchronized two sub-sequences of object arrays + * being compared, and at least one of the sequences is non-empty. Since the + * sequences are synchronized, the objects before the two sub-sequences are + * equals (if they exist). This property also holds for the objects after + * the two sub-sequences. + *

              + * The replacement is defined as replacing the from + * sub-sequence into the to sub-sequence. + * + * @param skipped number of tokens skipped since the last call (i.e. number of + * tokens that were in both sequences), this number should be strictly positive + * except on the very first call where it can be zero (if the first object of + * the two sequences are different) + * @param from sub-sequence of objects coming from the first sequence + * @param to sub-sequence of objects coming from the second sequence + */ + void handleReplacement(int skipped, List from, List to); + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/sequence/SequencesComparator.java b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/SequencesComparator.java new file mode 100644 index 000000000..dd57c0ae6 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/SequencesComparator.java @@ -0,0 +1,348 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.sequence; + +import java.util.List; + +import org.apache.commons.collections4.Equator; +import org.apache.commons.collections4.functors.DefaultEquator; + +/** + * This class allows to compare two objects sequences. + *

              + * The two sequences can hold any object type, as only the equals + * method is used to compare the elements of the sequences. It is guaranteed + * that the comparisons will always be done as o1.equals(o2) where + * o1 belongs to the first sequence and o2 belongs to + * the second sequence. This can be important if subclassing is used for some + * elements in the first sequence and the equals method is + * specialized. + *

              + * Comparison can be seen from two points of view: either as giving the smallest + * modification allowing to transform the first sequence into the second one, or + * as giving the longest sequence which is a subsequence of both initial + * sequences. The equals method is used to compare objects, so any + * object can be put into sequences. Modifications include deleting, inserting + * or keeping one object, starting from the beginning of the first sequence. + *

              + * This class implements the comparison algorithm, which is the very efficient + * algorithm from Eugene W. Myers + * + * An O(ND) Difference Algorithm and Its Variations. This algorithm produces + * the shortest possible + * {@link EditScript edit script} + * containing all the + * {@link EditCommand commands} + * needed to transform the first sequence into the second one. + * + * @see EditScript + * @see EditCommand + * @see CommandVisitor + * + * @since 4.0 + * @version $Id: SequencesComparator.java 1540567 2013-11-10 22:19:29Z tn $ + */ +public class SequencesComparator { + + /** First sequence. */ + private final List sequence1; + + /** Second sequence. */ + private final List sequence2; + + /** The equator used for testing object equality. */ + private final Equator equator; + + /** Temporary variables. */ + private final int[] vDown; + private final int[] vUp; + + /** + * Simple constructor. + *

              + * Creates a new instance of SequencesComparator using a {@link DefaultEquator}. + *

              + * It is guaranteed that the comparisons will always be done as + * o1.equals(o2) where o1 belongs to the first + * sequence and o2 belongs to the second sequence. This can be + * important if subclassing is used for some elements in the first sequence + * and the equals method is specialized. + * + * @param sequence1 first sequence to be compared + * @param sequence2 second sequence to be compared + */ + public SequencesComparator(final List sequence1, final List sequence2) { + this(sequence1, sequence2, DefaultEquator.defaultEquator()); + } + + /** + * Simple constructor. + *

              + * Creates a new instance of SequencesComparator with a custom {@link Equator}. + *

              + * It is guaranteed that the comparisons will always be done as + * Equator.equate(o1, o2) where o1 belongs to the first + * sequence and o2 belongs to the second sequence. + * + * @param sequence1 first sequence to be compared + * @param sequence2 second sequence to be compared + * @param equator the equator to use for testing object equality + */ + public SequencesComparator(final List sequence1, final List sequence2, final Equator equator) { + this.sequence1 = sequence1; + this.sequence2 = sequence2; + this.equator = equator; + + final int size = sequence1.size() + sequence2.size() + 2; + vDown = new int[size]; + vUp = new int[size]; + } + + /** + * Get the {@link EditScript} object. + *

              + * It is guaranteed that the objects embedded in the {@link InsertCommand + * insert commands} come from the second sequence and that the objects + * embedded in either the {@link DeleteCommand delete commands} or + * {@link KeepCommand keep commands} come from the first sequence. This can + * be important if subclassing is used for some elements in the first + * sequence and the equals method is specialized. + * + * @return the edit script resulting from the comparison of the two + * sequences + */ + public EditScript getScript() { + final EditScript script = new EditScript(); + buildScript(0, sequence1.size(), 0, sequence2.size(), script); + return script; + } + + /** + * Build a snake. + * + * @param start the value of the start of the snake + * @param diag the value of the diagonal of the snake + * @param end1 the value of the end of the first sequence to be compared + * @param end2 the value of the end of the second sequence to be compared + * @return the snake built + */ + private Snake buildSnake(final int start, final int diag, final int end1, final int end2) { + int end = start; + while (end - diag < end2 + && end < end1 + && equator.equate(sequence1.get(end), sequence2.get(end - diag))) { + ++end; + } + return new Snake(start, end, diag); + } + + /** + * Get the middle snake corresponding to two subsequences of the + * main sequences. + *

              + * The snake is found using the MYERS Algorithm (this algorithms has + * also been implemented in the GNU diff program). This algorithm is + * explained in Eugene Myers article: + * + * An O(ND) Difference Algorithm and Its Variations. + * + * @param start1 the begin of the first sequence to be compared + * @param end1 the end of the first sequence to be compared + * @param start2 the begin of the second sequence to be compared + * @param end2 the end of the second sequence to be compared + * @return the middle snake + */ + private Snake getMiddleSnake(final int start1, final int end1, final int start2, final int end2) { + // Myers Algorithm + // Initialisations + final int m = end1 - start1; + final int n = end2 - start2; + if (m == 0 || n == 0) { + return null; + } + + final int delta = m - n; + final int sum = n + m; + final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2; + vDown[1+offset] = start1; + vUp[1+offset] = end1 + 1; + + for (int d = 0; d <= offset ; ++d) { + // Down + for (int k = -d; k <= d; k += 2) { + // First step + + final int i = k + offset; + if (k == -d || k != d && vDown[i-1] < vDown[i+1]) { + vDown[i] = vDown[i+1]; + } else { + vDown[i] = vDown[i-1] + 1; + } + + int x = vDown[i]; + int y = x - start1 + start2 - k; + + while (x < end1 && y < end2 && equator.equate(sequence1.get(x), sequence2.get(y))) { + vDown[i] = ++x; + ++y; + } + // Second step + if (delta % 2 != 0 && delta - d <= k && k <= delta + d) { + if (vUp[i-delta] <= vDown[i]) { + return buildSnake(vUp[i-delta], k + start1 - start2, end1, end2); + } + } + } + + // Up + for (int k = delta - d; k <= delta + d; k += 2) { + // First step + final int i = k + offset - delta; + if (k == delta - d + || k != delta + d && vUp[i+1] <= vUp[i-1]) { + vUp[i] = vUp[i+1] - 1; + } else { + vUp[i] = vUp[i-1]; + } + + int x = vUp[i] - 1; + int y = x - start1 + start2 - k; + while (x >= start1 && y >= start2 + && equator.equate(sequence1.get(x), sequence2.get(y))) { + vUp[i] = x--; + y--; + } + // Second step + if (delta % 2 == 0 && -d <= k && k <= d ) { + if (vUp[i] <= vDown[i + delta]) { + return buildSnake(vUp[i], k + start1 - start2, end1, end2); + } + } + } + } + + // this should not happen + throw new RuntimeException("Internal Error"); + } + + + /** + * Build an edit script. + * + * @param start1 the begin of the first sequence to be compared + * @param end1 the end of the first sequence to be compared + * @param start2 the begin of the second sequence to be compared + * @param end2 the end of the second sequence to be compared + * @param script the edited script + */ + private void buildScript(final int start1, final int end1, final int start2, final int end2, + final EditScript script) { + + final Snake middle = getMiddleSnake(start1, end1, start2, end2); + + if (middle == null + || middle.getStart() == end1 && middle.getDiag() == end1 - end2 + || middle.getEnd() == start1 && middle.getDiag() == start1 - start2) { + + int i = start1; + int j = start2; + while (i < end1 || j < end2) { + if (i < end1 && j < end2 && equator.equate(sequence1.get(i), sequence2.get(j))) { + script.append(new KeepCommand(sequence1.get(i))); + ++i; + ++j; + } else { + if (end1 - start1 > end2 - start2) { + script.append(new DeleteCommand(sequence1.get(i))); + ++i; + } else { + script.append(new InsertCommand(sequence2.get(j))); + ++j; + } + } + } + + } else { + + buildScript(start1, middle.getStart(), + start2, middle.getStart() - middle.getDiag(), + script); + for (int i = middle.getStart(); i < middle.getEnd(); ++i) { + script.append(new KeepCommand(sequence1.get(i))); + } + buildScript(middle.getEnd(), end1, + middle.getEnd() - middle.getDiag(), end2, + script); + } + } + + /** + * This class is a simple placeholder to hold the end part of a path + * under construction in a {@link SequencesComparator SequencesComparator}. + */ + private static class Snake { + + /** Start index. */ + private final int start; + + /** End index. */ + private final int end; + + /** Diagonal number. */ + private final int diag; + + /** + * Simple constructor. Creates a new instance of Snake with specified indices. + * + * @param start start index of the snake + * @param end end index of the snake + * @param diag diagonal number + */ + public Snake(final int start, final int end, final int diag) { + this.start = start; + this.end = end; + this.diag = diag; + } + + /** + * Get the start index of the snake. + * + * @return start index of the snake + */ + public int getStart() { + return start; + } + + /** + * Get the end index of the snake. + * + * @return end index of the snake + */ + public int getEnd() { + return end; + } + + /** + * Get the diagonal number of the snake. + * + * @return diagonal number of the snake + */ + public int getDiag() { + return diag; + } + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/sequence/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/package-info.java new file mode 100644 index 000000000..42011eb3d --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/sequence/package-info.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package provides classes to compare two sequences of objects. + *

              + * The two sequences can hold any object type, as only the + * equals method is used to compare the elements of the + * sequences. It is guaranteed that the comparisons will always be done + * as o1.equals(o2) where o1 belongs to the + * first sequence and o2 belongs to the second + * sequence. This can be important if subclassing is used for some + * elements in the first sequence and the equals method is + * specialized. + *

              + * Comparison can be seen from two points of view: either as giving the + * smallest modification allowing to transform the first sequence into + * the second one, or as giving the longest sequence which is a + * subsequence of both initial sequences. The equals method + * is used to compare objects, so any object can be put into + * sequences. Modifications include deleting, inserting or keeping one + * object, starting from the beginning of the first sequence. Like most + * algorithms of the same type, objects transpositions are not + * supported. This means that if a sequence (A, B) is + * compared to (B, A), the result will be either the + * sequence of three commands delete A, keep B, + * insert A or the sequence insert B, + * keep A, delete B. + *

              + * The package uses a very efficient comparison algorithm designed by + * Eugene W. Myers and described in his paper: An O(ND) + * Difference Algorithm and Its Variations. This algorithm produces + * the shortest possible + * {@link org.apache.commons.collections4.sequence.EditScript edit script} containing + * all the {@link org.apache.commons.collections4.sequence.EditCommand commands} + * needed to transform the first sequence into the second one. + * The entry point for the user to this algorithm is the + * {@link org.apache.commons.collections4.sequence.SequencesComparator} class. + *

              + * As explained in Gene Myers paper, the edit script is equivalent to all + * other representations and contains all the needed information either + * to perform the transformation, of course, or to retrieve the longest + * common subsequence for example. + *

              + * If the user needs a very fine grained access to the comparison result, + * he needs to go through this script by providing a visitor implementing + * the {@link org.apache.commons.collections4.sequence.CommandVisitor} interface. + *

              + * Sometimes however, a more synthetic approach is needed. If the user + * prefers to see the differences between the two sequences as global + * replacement operations acting on complete subsequences of + * the original sequences, he will provide an object implementing the + * simple {@link org.apache.commons.collections4.sequence.ReplacementsHandler} interface, + * using an instance of the {@link org.apache.commons.collections4.sequence.ReplacementsFinder} + * class as a command converting layer between his object and the edit script. The number of + * objects which are common to both initial arrays and hence are skipped between each call to the user + * {@link org.apache.commons.collections4.sequence.ReplacementsHandler#handleReplacement handleReplacement} + * method is also provided. This allows the user to keep track of the current index in + * both arrays if he needs so. + * + * @version $Id: package-info.java 1479338 2013-05-05 15:21:44Z tn $ + */ +package org.apache.commons.collections4.sequence; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractNavigableSetDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractNavigableSetDecorator.java new file mode 100644 index 000000000..3b4aface8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractNavigableSetDecorator.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.Iterator; +import java.util.NavigableSet; + +/** + * Decorates another NavigableSet to provide additional behaviour. + *

              + * Methods are forwarded directly to the decorated set. + * + * @param the type of the elements in the navigable set + * @since 4.1 + * @version $Id: AbstractNavigableSetDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractNavigableSetDecorator + extends AbstractSortedSetDecorator + implements NavigableSet { + + /** Serialization version */ + private static final long serialVersionUID = 20150528L; + + /** + * Constructor only used in deserialization, do not use otherwise. + */ + protected AbstractNavigableSetDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param set the set to decorate, must not be null + * @throws NullPointerException if set is null + */ + protected AbstractNavigableSetDecorator(final NavigableSet set) { + super(set); + } + + /** + * Gets the set being decorated. + * + * @return the decorated set + */ + @Override + protected NavigableSet decorated() { + return (NavigableSet) super.decorated(); + } + + //----------------------------------------------------------------------- + + @Override + public E lower(E e) { + return decorated().lower(e); + } + + @Override + public E floor(E e) { + return decorated().floor(e); + } + + @Override + public E ceiling(E e) { + return decorated().ceiling(e); + } + + @Override + public E higher(E e) { + return decorated().higher(e); + } + + @Override + public E pollFirst() { + return decorated().pollFirst(); + } + + @Override + public E pollLast() { + return decorated().pollLast(); + } + + @Override + public NavigableSet descendingSet() { + return decorated().descendingSet(); + } + + @Override + public Iterator descendingIterator() { + return decorated().descendingIterator(); + } + + @Override + public NavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return decorated().subSet(fromElement, fromInclusive, toElement, toInclusive); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + return decorated().headSet(toElement, inclusive); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return decorated().tailSet(fromElement, inclusive); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSerializableSetDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSerializableSetDecorator.java new file mode 100644 index 000000000..312f9b416 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSerializableSetDecorator.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Set; + +/** + * Serializable subclass of AbstractSetDecorator. + * + * @since 3.1 + * @version $Id: AbstractSerializableSetDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractSerializableSetDecorator + extends AbstractSetDecorator { + + /** Serialization version */ + private static final long serialVersionUID = 1229469966212206107L; + + /** + * Constructor. + * + * @param set the list to decorate, must not be null + * @throws NullPointerException if set is null + */ + protected AbstractSerializableSetDecorator(final Set set) { + super(set); + } + + //----------------------------------------------------------------------- + /** + * Write the set out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the set in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setCollection((Collection) in.readObject()); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSetDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSetDecorator.java new file mode 100644 index 000000000..8c6358ee8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSetDecorator.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.Set; + +import org.apache.commons.collections4.collection.AbstractCollectionDecorator; + +/** + * Decorates another Set to provide additional behaviour. + *

              + * Methods are forwarded directly to the decorated set. + * + * @param the type of the elements in the set + * @since 3.0 + * @version $Id: AbstractSetDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractSetDecorator extends AbstractCollectionDecorator implements + Set { + + /** Serialization version */ + private static final long serialVersionUID = -4678668309576958546L; + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since 3.1 + */ + protected AbstractSetDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param set the set to decorate, must not be null + * @throws NullPointerException if set is null + */ + protected AbstractSetDecorator(final Set set) { + super(set); + } + + /** + * Gets the set being decorated. + * + * @return the decorated set + */ + @Override + protected Set decorated() { + return (Set) super.decorated(); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSortedSetDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSortedSetDecorator.java new file mode 100644 index 000000000..3740e1285 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/AbstractSortedSetDecorator.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.Comparator; +import java.util.Set; +import java.util.SortedSet; + +/** + * Decorates another SortedSet to provide additional behaviour. + *

              + * Methods are forwarded directly to the decorated set. + * + * @param the type of the elements in the sorted set + * @since 3.0 + * @version $Id: AbstractSortedSetDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public abstract class AbstractSortedSetDecorator + extends AbstractSetDecorator + implements SortedSet { + + /** Serialization version */ + private static final long serialVersionUID = -3462240946294214398L; + + /** + * Constructor only used in deserialization, do not use otherwise. + * @since 3.1 + */ + protected AbstractSortedSetDecorator() { + super(); + } + + /** + * Constructor that wraps (not copies). + * + * @param set the set to decorate, must not be null + * @throws NullPointerException if set is null + */ + protected AbstractSortedSetDecorator(final Set set) { + super(set); + } + + /** + * Gets the set being decorated. + * + * @return the decorated set + */ + @Override + protected SortedSet decorated() { + return (SortedSet) super.decorated(); + } + + //----------------------------------------------------------------------- + public SortedSet subSet(final E fromElement, final E toElement) { + return decorated().subSet(fromElement, toElement); + } + + public SortedSet headSet(final E toElement) { + return decorated().headSet(toElement); + } + + public SortedSet tailSet(final E fromElement) { + return decorated().tailSet(fromElement); + } + + public E first() { + return decorated().first(); + } + + public E last() { + return decorated().last(); + } + + public Comparator comparator() { + return decorated().comparator(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/CompositeSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/CompositeSet.java new file mode 100644 index 000000000..aa2782ec7 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/CompositeSet.java @@ -0,0 +1,498 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.iterators.EmptyIterator; +import org.apache.commons.collections4.iterators.IteratorChain; +import org.apache.commons.collections4.list.UnmodifiableList; + +/** + * Decorates a set of other sets to provide a single unified view. + *

              + * Changes made to this set will actually be made on the decorated set. + * Add operations require the use of a pluggable strategy. + * If no strategy is provided then add is unsupported. + *

              + * From version 4.0, this class does not extend + * {@link org.apache.commons.collections4.collection.CompositeCollection CompositeCollection} + * anymore due to its input restrictions (only accepts Sets). + * See COLLECTIONS-424 + * for more details. + * + * @since 3.0 + * @version $Id: CompositeSet.java 1543273 2013-11-19 00:52:40Z ggregory $ + */ +public class CompositeSet implements Set, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 5185069727540378940L; + + /** SetMutator to handle changes to the collection */ + private SetMutator mutator; + + /** Sets in the composite */ + private final List> all = new ArrayList>(); + + /** + * Create an empty CompositeSet. + */ + public CompositeSet() { + super(); + } + + /** + * Create a CompositeSet with just set composited. + * + * @param set the initial set in the composite + */ + public CompositeSet(final Set set) { + super(); + addComposited(set); + } + + /** + * Create a composite set with sets as the initial set of composited Sets. + * + * @param sets the initial sets in the composite + */ + public CompositeSet(final Set... sets) { + super(); + addComposited(sets); + } + + //----------------------------------------------------------------------- + /** + * Gets the size of this composite set. + *

              + * This implementation calls size() on each set. + * + * @return total number of elements in all contained containers + */ + public int size() { + int size = 0; + for (final Set item : all) { + size += item.size(); + } + return size; + } + + /** + * Checks whether this composite set is empty. + *

              + * This implementation calls isEmpty() on each set. + * + * @return true if all of the contained sets are empty + */ + public boolean isEmpty() { + for (final Set item : all) { + if (item.isEmpty() == false) { + return false; + } + } + return true; + } + + /** + * Checks whether this composite set contains the object. + *

              + * This implementation calls contains() on each set. + * + * @param obj the object to search for + * @return true if obj is contained in any of the contained sets + */ + public boolean contains(final Object obj) { + for (final Set item : all) { + if (item.contains(obj)) { + return true; + } + } + return false; + } + + /** + * Gets an iterator over all the sets in this composite. + *

              + * This implementation uses an IteratorChain. + * + * @return an IteratorChain instance which supports + * remove(). Iteration occurs over contained collections in + * the order they were added, but this behavior should not be relied upon. + * @see IteratorChain + */ + public Iterator iterator() { + if (all.isEmpty()) { + return EmptyIterator.emptyIterator(); + } + final IteratorChain chain = new IteratorChain(); + for (final Set item : all) { + chain.addIterator(item.iterator()); + } + return chain; + } + + /** + * Returns an array containing all of the elements in this composite. + * + * @return an object array of all the elements in the collection + */ + public Object[] toArray() { + final Object[] result = new Object[size()]; + int i = 0; + for (final Iterator it = iterator(); it.hasNext(); i++) { + result[i] = it.next(); + } + return result; + } + + /** + * Returns an object array, populating the supplied array if possible. + * See Collection interface for full details. + * + * @param the type of the elements in the collection + * @param array the array to use, populating if possible + * @return an array of all the elements in the collection + */ + @SuppressWarnings("unchecked") + public T[] toArray(final T[] array) { + final int size = size(); + Object[] result = null; + if (array.length >= size) { + result = array; + } else { + result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size); + } + + int offset = 0; + for (final Collection item : all) { + for (final E e : item) { + result[offset++] = e; + } + } + if (result.length > size) { + result[size] = null; + } + return (T[]) result; + } + + /** + * Adds an object to the collection, throwing UnsupportedOperationException + * unless a SetMutator strategy is specified. + * + * @param obj the object to add + * @return {@code true} if the collection was modified + * @throws UnsupportedOperationException if SetMutator hasn't been set or add is unsupported + * @throws ClassCastException if the object cannot be added due to its type + * @throws NullPointerException if the object cannot be added because its null + * @throws IllegalArgumentException if the object cannot be added + */ + public boolean add(final E obj) { + if (mutator == null) { + throw new UnsupportedOperationException( + "add() is not supported on CompositeSet without a SetMutator strategy"); + } + return mutator.add(this, all, obj); + } + + /** + * If a CollectionMutator is defined for this CompositeSet then this + * method will be called anyway. + * + * @param obj object to be removed + * @return true if the object is removed, false otherwise + */ + public boolean remove(final Object obj) { + for (final Set set : getSets()) { + if (set.contains(obj)) { + return set.remove(obj); + } + } + return false; + } + + /** + * Checks whether this composite contains all the elements in the specified collection. + *

              + * This implementation calls contains() for each element in the + * specified collection. + * + * @param coll the collection to check for + * @return true if all elements contained + */ + public boolean containsAll(final Collection coll) { + for (final Object item : coll) { + if (contains(item) == false) { + return false; + } + } + return true; + } + + /** + * Adds a collection of elements to this composite, throwing + * UnsupportedOperationException unless a SetMutator strategy is specified. + * + * @param coll the collection to add + * @return true if the composite was modified + * @throws UnsupportedOperationException if SetMutator hasn't been set or add is unsupported + * @throws ClassCastException if the object cannot be added due to its type + * @throws NullPointerException if the object cannot be added because its null + * @throws IllegalArgumentException if the object cannot be added + */ + public boolean addAll(final Collection coll) { + if (mutator == null) { + throw new UnsupportedOperationException( + "addAll() is not supported on CompositeSet without a SetMutator strategy"); + } + return mutator.addAll(this, all, coll); + } + + /** + * Removes the elements in the specified collection from this composite set. + *

              + * This implementation calls removeAll on each collection. + * + * @param coll the collection to remove + * @return true if the composite was modified + * @throws UnsupportedOperationException if removeAll is unsupported + */ + public boolean removeAll(final Collection coll) { + if (coll.size() == 0) { + return false; + } + boolean changed = false; + for (final Collection item : all) { + changed |= item.removeAll(coll); + } + return changed; + } + + /** + * Retains all the elements in the specified collection in this composite set, + * removing all others. + *

              + * This implementation calls retainAll() on each collection. + * + * @param coll the collection to remove + * @return true if the composite was modified + * @throws UnsupportedOperationException if retainAll is unsupported + */ + public boolean retainAll(final Collection coll) { + boolean changed = false; + for (final Collection item : all) { + changed |= item.retainAll(coll); + } + return changed; + } + + /** + * Removes all of the elements from this composite set. + *

              + * This implementation calls clear() on each set. + * + * @throws UnsupportedOperationException if clear is unsupported + */ + public void clear() { + for (final Collection coll : all) { + coll.clear(); + } + } + + //----------------------------------------------------------------------- + /** + * Specify a SetMutator strategy instance to handle changes. + * + * @param mutator the mutator to use + */ + public void setMutator(final SetMutator mutator) { + this.mutator = mutator; + } + + /** + * Add a Set to this composite. + * + * @param set the set to add + * @throws IllegalArgumentException if a SetMutator is set, but fails to resolve a collision + * @throws UnsupportedOperationException if there is no SetMutator set + * @throws NullPointerException if {@code set} is null + * @see SetMutator + */ + public synchronized void addComposited(final Set set) { + for (final Set existingSet : getSets()) { + final Collection intersects = CollectionUtils.intersection(existingSet, set); + if (intersects.size() > 0) { + if (this.mutator == null) { + throw new UnsupportedOperationException( + "Collision adding composited set with no SetMutator set"); + } + getMutator().resolveCollision(this, existingSet, set, intersects); + if (CollectionUtils.intersection(existingSet, set).size() > 0) { + throw new IllegalArgumentException( + "Attempt to add illegal entry unresolved by SetMutator.resolveCollision()"); + } + } + } + all.add(set); + } + + /** + * Add these Sets to the list of sets in this composite. + * + * @param set1 the first Set to be appended to the composite + * @param set2 the second Set to be appended to the composite + */ + public void addComposited(final Set set1, final Set set2) { + addComposited(set1); + addComposited(set2); + } + + /** + * Add these Sets to the list of sets in this composite + * + * @param sets the Sets to be appended to the composite + */ + public void addComposited(final Set... sets) { + for (Set set : sets) { + addComposited(set); + } + } + + /** + * Removes a set from those being decorated in this composite. + * + * @param set set to be removed + */ + public void removeComposited(final Set set) { + all.remove(set); + } + + //----------------------------------------------------------------------- + /** + * Returns a new Set containing all of the elements. + * + * @return A new HashSet containing all of the elements in this composite. + * The new collection is not backed by this composite. + */ + public Set toSet() { + return new HashSet(this); + } + + /** + * Gets the sets being decorated. + * + * @return Unmodifiable list of all sets in this composite. + */ + public List> getSets() { + return UnmodifiableList.unmodifiableList(all); + } + + /** + * Get the set mutator to be used for this CompositeSet. + * @return the set mutator + */ + protected SetMutator getMutator() { + return mutator; + } + + /** + * {@inheritDoc} + * @see java.util.Set#equals + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof Set) { + final Set set = (Set) obj; + return set.size() == this.size() && set.containsAll(this); + } + return false; + } + + /** + * {@inheritDoc} + * @see java.util.Set#hashCode + */ + @Override + public int hashCode() { + int code = 0; + for (final E e : this) { + code += e == null ? 0 : e.hashCode(); + } + return code; + } + + /** + * Define callbacks for mutation operations. + */ + public static interface SetMutator extends Serializable { + + /** + * Called when an object is to be added to the composite. + * + * @param composite the CompositeSet being changed + * @param sets all of the Set instances in this CompositeSet + * @param obj the object being added + * @return true if the collection is changed + * @throws UnsupportedOperationException if add is unsupported + * @throws ClassCastException if the object cannot be added due to its type + * @throws NullPointerException if the object cannot be added because its null + * @throws IllegalArgumentException if the object cannot be added + */ + boolean add(CompositeSet composite, List> sets, E obj); + + /** + * Called when a collection is to be added to the composite. + * + * @param composite the CompositeSet being changed + * @param sets all of the Set instances in this CompositeSet + * @param coll the collection being added + * @return true if the collection is changed + * @throws UnsupportedOperationException if add is unsupported + * @throws ClassCastException if the object cannot be added due to its type + * @throws NullPointerException if the object cannot be added because its null + * @throws IllegalArgumentException if the object cannot be added + */ + boolean addAll(CompositeSet composite, + List> sets, + Collection coll); + + /** + * Called when a Set is added to the CompositeSet and there is a + * collision between existing and added sets. + *

              + * If added and existing still have any intersects + * after this method returns an IllegalArgumentException will be thrown. + * + * @param comp the CompositeSet being modified + * @param existing the Set already existing in the composite + * @param added the Set being added to the composite + * @param intersects the intersection of the existing and added sets + */ + void resolveCollision(CompositeSet comp, + Set existing, + Set added, + Collection intersects); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/ListOrderedSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/ListOrderedSet.java new file mode 100644 index 000000000..6ec35c79e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/ListOrderedSet.java @@ -0,0 +1,409 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.OrderedIterator; +import org.apache.commons.collections4.functors.UniquePredicate; +import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; +import org.apache.commons.collections4.list.UnmodifiableList; + +/** + * Decorates another Set to ensure that the order of addition is + * retained and used by the iterator. + *

              + * If an object is added to the set for a second time, it will remain in the + * original position in the iteration. The order can be observed from the set + * via the iterator or toArray methods. + *

              + * The ListOrderedSet also has various useful direct methods. These include many + * from List, such as get(int), + * remove(int) and indexOf(int). An unmodifiable + * List view of the set can be obtained via asList(). + *

              + * This class cannot implement the List interface directly as + * various interface methods (notably equals/hashCode) are incompatible with a + * set. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: ListOrderedSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class ListOrderedSet + extends AbstractSerializableSetDecorator { + + /** Serialization version */ + private static final long serialVersionUID = -228664372470420141L; + + /** Internal list to hold the sequence of objects */ + private final List setOrder; + + /** + * Factory method to create an ordered set specifying the list and set to use. + *

              + * The list and set must both be empty. + * + * @param the element type + * @param set the set to decorate, must be empty and not null + * @param list the list to decorate, must be empty and not null + * @return a new ordered set + * @throws NullPointerException if set or list is null + * @throws IllegalArgumentException if either the set or list is not empty + * @since 4.0 + */ + public static ListOrderedSet listOrderedSet(final Set set, final List list) { + if (set == null) { + throw new NullPointerException("Set must not be null"); + } + if (list == null) { + throw new NullPointerException("List must not be null"); + } + if (set.size() > 0 || list.size() > 0) { + throw new IllegalArgumentException("Set and List must be empty"); + } + return new ListOrderedSet(set, list); + } + + /** + * Factory method to create an ordered set. + *

              + * An ArrayList is used to retain order. + * + * @param the element type + * @param set the set to decorate, must not be null + * @return a new ordered set + * @throws NullPointerException if set is null + * @since 4.0 + */ + public static ListOrderedSet listOrderedSet(final Set set) { + return new ListOrderedSet(set); + } + + /** + * Factory method to create an ordered set using the supplied list to retain order. + *

              + * A HashSet is used for the set behaviour. + *

              + * NOTE: If the list contains duplicates, the duplicates are removed, + * altering the specified list. + * + * @param the element type + * @param list the list to decorate, must not be null + * @return a new ordered set + * @throws NullPointerException if list is null + * @since 4.0 + */ + public static ListOrderedSet listOrderedSet(final List list) { + if (list == null) { + throw new NullPointerException("List must not be null"); + } + CollectionUtils.filter(list, UniquePredicate.uniquePredicate()); + final Set set = new HashSet(list); + + return new ListOrderedSet(set, list); + } + + // ----------------------------------------------------------------------- + /** + * Constructs a new empty ListOrderedSet using a + * HashSet and an ArrayList internally. + * + * @since 3.1 + */ + public ListOrderedSet() { + super(new HashSet()); + setOrder = new ArrayList(); + } + + /** + * Constructor that wraps (not copies). + * + * @param set the set to decorate, must not be null + * @throws IllegalArgumentException if set is null + */ + protected ListOrderedSet(final Set set) { + super(set); + setOrder = new ArrayList(set); + } + + /** + * Constructor that wraps (not copies) the Set and specifies the list to + * use. + *

              + * The set and list must both be correctly initialised to the same elements. + * + * @param set the set to decorate, must not be null + * @param list the list to decorate, must not be null + * @throws NullPointerException if set or list is null + */ + protected ListOrderedSet(final Set set, final List list) { + super(set); + if (list == null) { + throw new NullPointerException("List must not be null"); + } + setOrder = list; + } + + // ----------------------------------------------------------------------- + /** + * Gets an unmodifiable view of the order of the Set. + * + * @return an unmodifiable list view + */ + public List asList() { + return UnmodifiableList.unmodifiableList(setOrder); + } + + // ----------------------------------------------------------------------- + @Override + public void clear() { + decorated().clear(); + setOrder.clear(); + } + + @Override + public OrderedIterator iterator() { + return new OrderedSetIterator(setOrder.listIterator(), decorated()); + } + + @Override + public boolean add(final E object) { + if (decorated().add(object)) { + setOrder.add(object); + return true; + } + return false; + } + + @Override + public boolean addAll(final Collection coll) { + boolean result = false; + for (final E e : coll) { + result |= add(e); + } + return result; + } + + @Override + public boolean remove(final Object object) { + final boolean result = decorated().remove(object); + if (result) { + setOrder.remove(object); + } + return result; + } + + @Override + public boolean removeAll(final Collection coll) { + boolean result = false; + for (final Object name : coll) { + result |= remove(name); + } + return result; + } + + /** + * {@inheritDoc} + *

              + * This implementation iterates over the elements of this set, checking + * each element in turn to see if it's contained in coll. + * If it's not contained, it's removed from this set. As a consequence, + * it is advised to use a collection type for coll that provides + * a fast (e.g. O(1)) implementation of {@link Collection#contains(Object)}. + */ + @Override + public boolean retainAll(final Collection coll) { + boolean result = decorated().retainAll(coll); + if (result == false) { + return false; + } + if (decorated().size() == 0) { + setOrder.clear(); + } else { + for (Iterator it = setOrder.iterator(); it.hasNext();) { + if (!decorated().contains(it.next())) { + it.remove(); + } + } + } + return result; + } + + @Override + public Object[] toArray() { + return setOrder.toArray(); + } + + @Override + public T[] toArray(final T a[]) { + return setOrder.toArray(a); + } + + // ----------------------------------------------------------------------- + // Additional methods that comply to the {@link List} interface + // ----------------------------------------------------------------------- + + /** + * Returns the element at the specified position in this ordered set. + * + * @param index the position of the element in the ordered {@link Set}. + * @return the element at position {@code index} + * @see List#get(int) + */ + public E get(final int index) { + return setOrder.get(index); + } + + /** + * Returns the index of the first occurrence of the specified element in + * ordered set. + * + * @param object the element to search for + * @return the index of the first occurrence of the object, or {@code -1} if + * this ordered set does not contain this object + * @see List#indexOf(Object) + */ + public int indexOf(final Object object) { + return setOrder.indexOf(object); + } + + /** + * Inserts the specified element at the specified position if it is not yet + * contained in this ordered set (optional operation). Shifts the element + * currently at this position and any subsequent elements to the right. + * + * @param index the index at which the element is to be inserted + * @param object the element to be inserted + * @see List#add(int, Object) + */ + public void add(final int index, final E object) { + if (!contains(object)) { + decorated().add(object); + setOrder.add(index, object); + } + } + + /** + * Inserts all elements in the specified collection not yet contained in the + * ordered set at the specified position (optional operation). Shifts the + * element currently at the position and all subsequent elements to the + * right. + * + * @param index the position to insert the elements + * @param coll the collection containing the elements to be inserted + * @return {@code true} if this ordered set changed as a result of the call + * @see List#addAll(int, Collection) + */ + public boolean addAll(final int index, final Collection coll) { + boolean changed = false; + // collect all elements to be added for performance reasons + final List toAdd = new ArrayList(); + for (final E e : coll) { + if (contains(e)) { + continue; + } + decorated().add(e); + toAdd.add(e); + changed = true; + } + + if (changed) { + setOrder.addAll(index, toAdd); + } + + return changed; + } + + /** + * Removes the element at the specified position from the ordered set. + * Shifts any subsequent elements to the left. + * + * @param index the index of the element to be removed + * @return the element that has been remove from the ordered set + * @see List#remove(int) + */ + public E remove(final int index) { + final E obj = setOrder.remove(index); + remove(obj); + return obj; + } + + /** + * Uses the underlying List's toString so that order is achieved. This means + * that the decorated Set's toString is not used, so any custom toStrings + * will be ignored. + * + * @return a string representation of the ordered set + */ + // Fortunately List.toString and Set.toString look the same + @Override + public String toString() { + return setOrder.toString(); + } + + // ----------------------------------------------------------------------- + /** + * Internal iterator handle remove. + */ + static class OrderedSetIterator + extends AbstractIteratorDecorator + implements OrderedIterator { + + /** Object we iterate on */ + private final Collection set; + + /** Last object retrieved */ + private E last; + + private OrderedSetIterator(final ListIterator iterator, final Collection set) { + super(iterator); + this.set = set; + } + + @Override + public E next() { + last = getIterator().next(); + return last; + } + + @Override + public void remove() { + set.remove(last); + getIterator().remove(); + last = null; + } + + public boolean hasPrevious() { + return ((ListIterator) getIterator()).hasPrevious(); + } + + public E previous() { + last = ((ListIterator) getIterator()).previous(); + return last; + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/MapBackedSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/MapBackedSet.java new file mode 100644 index 000000000..399d3a302 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/MapBackedSet.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Decorates a Map to obtain Set behaviour. + *

              + * This class is used to create a Set with the same properties as + * the key set of any map. Thus, a ReferenceSet can be created by wrapping a + * ReferenceMap in an instance of this class. + *

              + * Most map implementation can be used to create a set by passing in dummy values. + * Exceptions include BidiMap implementations, as they require unique values. + * + * @since 3.1 + * @version $Id: MapBackedSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class MapBackedSet implements Set, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 6723912213766056587L; + + /** The map being used as the backing store */ + private final Map map; + + /** The dummyValue to use */ + private final V dummyValue; + + /** + * Factory method to create a set from a map. + * + * @param the element type + * @param the dummy value type in the map + * @param map the map to decorate, must not be null + * @return a new map backed set + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static MapBackedSet mapBackedSet(final Map map) { + return mapBackedSet(map, null); + } + + /** + * Factory method to create a set from a map. + * + * @param the element type + * @param the dummy value type in the map + * @param map the map to decorate, must not be null + * @param dummyValue the dummy value to use + * @return a new map backed set + * @throws NullPointerException if map is null + * @since 4.0 + */ + public static MapBackedSet mapBackedSet(final Map map, final V dummyValue) { + return new MapBackedSet(map, dummyValue); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param map the map to decorate, must not be null + * @param dummyValue the dummy value to use + * @throws NullPointerException if map is null + */ + private MapBackedSet(final Map map, final V dummyValue) { + super(); + if (map == null) { + throw new NullPointerException("The map must not be null"); + } + this.map = map; + this.dummyValue = dummyValue; + } + + //----------------------------------------------------------------------- + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public Iterator iterator() { + return map.keySet().iterator(); + } + + public boolean contains(final Object obj) { + return map.containsKey(obj); + } + + public boolean containsAll(final Collection coll) { + return map.keySet().containsAll(coll); + } + + public boolean add(final E obj) { + final int size = map.size(); + map.put(obj, dummyValue); + return map.size() != size; + } + + public boolean addAll(final Collection coll) { + final int size = map.size(); + for (final E e : coll) { + map.put(e, dummyValue); + } + return map.size() != size; + } + + public boolean remove(final Object obj) { + final int size = map.size(); + map.remove(obj); + return map.size() != size; + } + + public boolean removeAll(final Collection coll) { + return map.keySet().removeAll(coll); + } + + public boolean retainAll(final Collection coll) { + return map.keySet().retainAll(coll); + } + + public void clear() { + map.clear(); + } + + public Object[] toArray() { + return map.keySet().toArray(); + } + + public T[] toArray(final T[] array) { + return map.keySet().toArray(array); + } + + @Override + public boolean equals(final Object obj) { + return map.keySet().equals(obj); + } + + @Override + public int hashCode() { + return map.keySet().hashCode(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedNavigableSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedNavigableSet.java new file mode 100644 index 000000000..3425f4c45 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedNavigableSet.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.Iterator; +import java.util.NavigableSet; + +import org.apache.commons.collections4.Predicate; + +/** + * Decorates another NavigableSet to validate that all additions + * match a specified predicate. + *

              + * This set exists to provide validation for the decorated set. + * It is normally created to decorate an empty set. + * If an object cannot be added to the set, an IllegalArgumentException is thrown. + *

              + * One usage would be to ensure that no null entries are added to the set. + *

              + * NavigableSet set =
              + *   PredicatedSortedSet.predicatedNavigableSet(new TreeSet(),
              + *                                              NotNullPredicate.notNullPredicate());
              + * 
              + * + * @since 4.1 + * @version $Id: PredicatedNavigableSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PredicatedNavigableSet extends PredicatedSortedSet implements NavigableSet { + + /** Serialization version */ + private static final long serialVersionUID = 20150528L; + + /** + * Factory method to create a predicated (validating) navigable set. + *

              + * If there are any elements already in the set being decorated, they + * are validated. + * + * @param the element type + * @param set the set to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @return a new predicated navigable set. + * @throws NullPointerException if set or predicate is null + * @throws IllegalArgumentException if the set contains invalid elements + * @since 4.0 + */ + public static PredicatedNavigableSet predicatedNavigableSet(final NavigableSet set, + final Predicate predicate) { + return new PredicatedNavigableSet(set, predicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the set being decorated, they + * are validated. + * + * @param set the set to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @throws NullPointerException if set or predicate is null + * @throws IllegalArgumentException if the set contains invalid elements + */ + protected PredicatedNavigableSet(final NavigableSet set, final Predicate predicate) { + super(set, predicate); + } + + /** + * Gets the navigable set being decorated. + * + * @return the decorated navigable set + */ + @Override + protected NavigableSet decorated() { + return (NavigableSet) super.decorated(); + } + + //----------------------------------------------------------------------- + + @Override + public E lower(E e) { + return decorated().lower(e); + } + + @Override + public E floor(E e) { + return decorated().floor(e); + } + + @Override + public E ceiling(E e) { + return decorated().ceiling(e); + } + + @Override + public E higher(E e) { + return decorated().higher(e); + } + + @Override + public E pollFirst() { + return decorated().pollFirst(); + } + + @Override + public E pollLast() { + return decorated().pollLast(); + } + + @Override + public NavigableSet descendingSet() { + return predicatedNavigableSet(decorated().descendingSet(), predicate); + } + + @Override + public Iterator descendingIterator() { + return decorated().descendingIterator(); + } + + @Override + public NavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + final NavigableSet sub = decorated().subSet(fromElement, fromInclusive, toElement, toInclusive); + return predicatedNavigableSet(sub, predicate); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + final NavigableSet head = decorated().headSet(toElement, inclusive); + return predicatedNavigableSet(head, predicate); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + final NavigableSet tail = decorated().tailSet(fromElement, inclusive); + return predicatedNavigableSet(tail, predicate); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedSet.java new file mode 100644 index 000000000..2b62ac8e2 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedSet.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.Set; + +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.collection.PredicatedCollection; + +/** + * Decorates another Set to validate that all additions + * match a specified predicate. + *

              + * This set exists to provide validation for the decorated set. + * It is normally created to decorate an empty set. + * If an object cannot be added to the set, an IllegalArgumentException is thrown. + *

              + * One usage would be to ensure that no null entries are added to the set. + *

              Set set = PredicatedSet.decorate(new HashSet(), NotNullPredicate.INSTANCE);
              + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: PredicatedSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PredicatedSet extends PredicatedCollection implements Set { + + /** Serialization version */ + private static final long serialVersionUID = -684521469108685117L; + + /** + * Factory method to create a predicated (validating) set. + *

              + * If there are any elements already in the set being decorated, they + * are validated. + * + * @param the element type + * @param set the set to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @return a decorated set + * @throws NullPointerException if set or predicate is null + * @throws IllegalArgumentException if the set contains invalid elements + * @since 4.0 + */ + public static PredicatedSet predicatedSet(final Set set, final Predicate predicate) { + return new PredicatedSet(set, predicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the set being decorated, they + * are validated. + * + * @param set the set to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @throws NullPointerException if set or predicate is null + * @throws IllegalArgumentException if the set contains invalid elements + */ + protected PredicatedSet(final Set set, final Predicate predicate) { + super(set, predicate); + } + + /** + * Gets the set being decorated. + * + * @return the decorated set + */ + @Override + protected Set decorated() { + return (Set) super.decorated(); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedSortedSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedSortedSet.java new file mode 100644 index 000000000..502392023 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/PredicatedSortedSet.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.Comparator; +import java.util.SortedSet; + +import org.apache.commons.collections4.Predicate; + +/** + * Decorates another SortedSet to validate that all additions + * match a specified predicate. + *

              + * This set exists to provide validation for the decorated set. + * It is normally created to decorate an empty set. + * If an object cannot be added to the set, an IllegalArgumentException is thrown. + *

              + * One usage would be to ensure that no null entries are added to the set. + *

              + * SortedSet set =
              + *   PredicatedSortedSet.predicatedSortedSet(new TreeSet(),
              + *                                           NotNullPredicate.notNullPredicate());
              + * 
              + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: PredicatedSortedSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class PredicatedSortedSet extends PredicatedSet implements SortedSet { + + /** Serialization version */ + private static final long serialVersionUID = -9110948148132275052L; + + /** + * Factory method to create a predicated (validating) sorted set. + *

              + * If there are any elements already in the set being decorated, they + * are validated. + * + * @param the element type + * @param set the set to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @return a new predicated sorted set. + * @throws NullPointerException if set or predicate is null + * @throws IllegalArgumentException if the set contains invalid elements + * @since 4.0 + */ + public static PredicatedSortedSet predicatedSortedSet(final SortedSet set, + final Predicate predicate) { + return new PredicatedSortedSet(set, predicate); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the set being decorated, they + * are validated. + * + * @param set the set to decorate, must not be null + * @param predicate the predicate to use for validation, must not be null + * @throws NullPointerException if set or predicate is null + * @throws IllegalArgumentException if the set contains invalid elements + */ + protected PredicatedSortedSet(final SortedSet set, final Predicate predicate) { + super(set, predicate); + } + + /** + * Gets the sorted set being decorated. + * + * @return the decorated sorted set + */ + @Override + protected SortedSet decorated() { + return (SortedSet) super.decorated(); + } + + //----------------------------------------------------------------------- + public Comparator comparator() { + return decorated().comparator(); + } + + public E first() { + return decorated().first(); + } + + public E last() { + return decorated().last(); + } + + public SortedSet subSet(final E fromElement, final E toElement) { + final SortedSet sub = decorated().subSet(fromElement, toElement); + return new PredicatedSortedSet(sub, predicate); + } + + public SortedSet headSet(final E toElement) { + final SortedSet head = decorated().headSet(toElement); + return new PredicatedSortedSet(head, predicate); + } + + public SortedSet tailSet(final E fromElement) { + final SortedSet tail = decorated().tailSet(fromElement); + return new PredicatedSortedSet(tail, predicate); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedNavigableSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedNavigableSet.java new file mode 100644 index 000000000..ddfe96eef --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedNavigableSet.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.Iterator; +import java.util.NavigableSet; + +import org.apache.commons.collections4.Transformer; + +/** + * Decorates another NavigableSet to transform objects that are added. + *

              + * The add methods are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must + * use the Integer form to remove objects. + * + * @since 4.1 + * @version $Id: TransformedNavigableSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TransformedNavigableSet extends TransformedSortedSet implements NavigableSet { + + /** Serialization version */ + private static final long serialVersionUID = 20150528L; + + /** + * Factory method to create a transforming navigable set. + *

              + * If there are any elements already in the set being decorated, they + * are NOT transformed. + * Contrast this with {@link #transformedNavigableSet(NavigableSet, Transformer)}. + * + * @param the element type + * @param set the set to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed {@link NavigableSet} + * @throws NullPointerException if set or transformer is null + */ + public static TransformedNavigableSet transformingNavigableSet(final NavigableSet set, + final Transformer transformer) { + return new TransformedNavigableSet(set, transformer); + } + + /** + * Factory method to create a transforming navigable set that will transform + * existing contents of the specified navigable set. + *

              + * If there are any elements already in the set being decorated, they + * will be transformed by this method. + * Contrast this with {@link #transformingNavigableSet(NavigableSet, Transformer)}. + * + * @param the element type + * @param set the set to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed {@link NavigableSet} + * @throws NullPointerException if set or transformer is null + */ + public static TransformedNavigableSet transformedNavigableSet(final NavigableSet set, + final Transformer transformer) { + + final TransformedNavigableSet decorated = new TransformedNavigableSet(set, transformer); + if (set.size() > 0) { + @SuppressWarnings("unchecked") // set is type E + final E[] values = (E[]) set.toArray(); // NOPMD - false positive for generics + set.clear(); + for (final E value : values) { + decorated.decorated().add(transformer.transform(value)); + } + } + return decorated; + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the set being decorated, they + * are NOT transformed. + * + * @param set the set to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @throws NullPointerException if set or transformer is null + */ + protected TransformedNavigableSet(final NavigableSet set, + final Transformer transformer) { + super(set, transformer); + } + + /** + * Gets the decorated navigable set. + * + * @return the decorated navigable set + */ + @Override + protected NavigableSet decorated() { + return (NavigableSet) super.decorated(); + } + + //----------------------------------------------------------------------- + + @Override + public E lower(E e) { + return decorated().lower(e); + } + + @Override + public E floor(E e) { + return decorated().floor(e); + } + + @Override + public E ceiling(E e) { + return decorated().ceiling(e); + } + + @Override + public E higher(E e) { + return decorated().higher(e); + } + + @Override + public E pollFirst() { + return decorated().pollFirst(); + } + + @Override + public E pollLast() { + return decorated().pollLast(); + } + + @Override + public NavigableSet descendingSet() { + return transformingNavigableSet(decorated().descendingSet(), transformer); + } + + @Override + public Iterator descendingIterator() { + return decorated().descendingIterator(); + } + + @Override + public NavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + final NavigableSet sub = decorated().subSet(fromElement, fromInclusive, toElement, toInclusive); + return transformingNavigableSet(sub, transformer); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + final NavigableSet head = decorated().headSet(toElement, inclusive); + return transformingNavigableSet(head, transformer); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + final NavigableSet tail = decorated().tailSet(fromElement, inclusive); + return transformingNavigableSet(tail, transformer); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedSet.java new file mode 100644 index 000000000..7bf774f4b --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedSet.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.Set; + +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.collection.TransformedCollection; + +/** + * Decorates another Set to transform objects that are added. + *

              + * The add methods are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must + * use the Integer form to remove objects. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: TransformedSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TransformedSet extends TransformedCollection implements Set { + + /** Serialization version */ + private static final long serialVersionUID = 306127383500410386L; + + /** + * Factory method to create a transforming set. + *

              + * If there are any elements already in the set being decorated, they + * are NOT transformed. + * Contrast this with {@link #transformedSet(Set, Transformer)}. + * + * @param the element type + * @param set the set to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed set + * @throws NullPointerException if set or transformer is null + * @since 4.0 + */ + public static TransformedSet transformingSet(final Set set, + final Transformer transformer) { + return new TransformedSet(set, transformer); + } + + /** + * Factory method to create a transforming set that will transform + * existing contents of the specified set. + *

              + * If there are any elements already in the set being decorated, they + * will be transformed by this method. + * Contrast this with {@link #transformingSet(Set, Transformer)}. + * + * @param the element type + * @param set the set to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed set + * @throws NullPointerException if set or transformer is null + * @since 4.0 + */ + public static Set transformedSet(final Set set, final Transformer transformer) { + final TransformedSet decorated = new TransformedSet(set, transformer); + if (set.size() > 0) { + @SuppressWarnings("unchecked") // set is type E + final E[] values = (E[]) set.toArray(); // NOPMD - false positive for generics + set.clear(); + for (final E value : values) { + decorated.decorated().add(transformer.transform(value)); + } + } + return decorated; + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the set being decorated, they + * are NOT transformed. + * + * @param set the set to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @throws NullPointerException if set or transformer is null + */ + protected TransformedSet(final Set set, final Transformer transformer) { + super(set, transformer); + } + + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedSortedSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedSortedSet.java new file mode 100644 index 000000000..ac3834c6b --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/TransformedSortedSet.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.Comparator; +import java.util.SortedSet; + +import org.apache.commons.collections4.Transformer; + +/** + * Decorates another SortedSet to transform objects that are added. + *

              + * The add methods are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must + * use the Integer form to remove objects. + *

              + * This class is Serializable from Commons Collections 3.1. + * + * @since 3.0 + * @version $Id: TransformedSortedSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class TransformedSortedSet extends TransformedSet implements SortedSet { + + /** Serialization version */ + private static final long serialVersionUID = -1675486811351124386L; + + /** + * Factory method to create a transforming sorted set. + *

              + * If there are any elements already in the set being decorated, they + * are NOT transformed. + * Contrast this with {@link #transformedSortedSet(SortedSet, Transformer)}. + * + * @param the element type + * @param set the set to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed {@link SortedSet} + * @throws NullPointerException if set or transformer is null + * @since 4.0 + */ + public static TransformedSortedSet transformingSortedSet(final SortedSet set, + final Transformer transformer) { + return new TransformedSortedSet(set, transformer); + } + + /** + * Factory method to create a transforming sorted set that will transform + * existing contents of the specified sorted set. + *

              + * If there are any elements already in the set being decorated, they + * will be transformed by this method. + * Contrast this with {@link #transformingSortedSet(SortedSet, Transformer)}. + * + * @param the element type + * @param set the set to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @return a new transformed {@link SortedSet} + * @throws NullPointerException if set or transformer is null + * @since 4.0 + */ + public static TransformedSortedSet transformedSortedSet(final SortedSet set, + final Transformer transformer) { + + final TransformedSortedSet decorated = new TransformedSortedSet(set, transformer); + if (set.size() > 0) { + @SuppressWarnings("unchecked") // set is type E + final E[] values = (E[]) set.toArray(); // NOPMD - false positive for generics + set.clear(); + for (final E value : values) { + decorated.decorated().add(transformer.transform(value)); + } + } + return decorated; + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the set being decorated, they + * are NOT transformed. + * + * @param set the set to decorate, must not be null + * @param transformer the transformer to use for conversion, must not be null + * @throws NullPointerException if set or transformer is null + */ + protected TransformedSortedSet(final SortedSet set, final Transformer transformer) { + super(set, transformer); + } + + /** + * Gets the decorated set. + * + * @return the decorated set + */ + protected SortedSet getSortedSet() { + return (SortedSet) decorated(); + } + + //----------------------------------------------------------------------- + public E first() { + return getSortedSet().first(); + } + + public E last() { + return getSortedSet().last(); + } + + public Comparator comparator() { + return getSortedSet().comparator(); + } + + //----------------------------------------------------------------------- + public SortedSet subSet(final E fromElement, final E toElement) { + final SortedSet set = getSortedSet().subSet(fromElement, toElement); + return new TransformedSortedSet(set, transformer); + } + + public SortedSet headSet(final E toElement) { + final SortedSet set = getSortedSet().headSet(toElement); + return new TransformedSortedSet(set, transformer); + } + + public SortedSet tailSet(final E fromElement) { + final SortedSet set = getSortedSet().tailSet(fromElement); + return new TransformedSortedSet(set, transformer); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableNavigableSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableNavigableSet.java new file mode 100644 index 000000000..3be017aa3 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableNavigableSet.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.SortedSet; + +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; + +/** + * Decorates another NavigableSet to ensure it can't be altered. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 4.1 + * @version $Id: UnmodifiableNavigableSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableNavigableSet + extends AbstractNavigableSetDecorator + implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = 20150528L; + + /** + * Factory method to create an unmodifiable set. + * + * @param the element type + * @param set the set to decorate, must not be null + * @return a new unmodifiable {@link NavigableSet} + * @throws NullPointerException if set is null + */ + public static NavigableSet unmodifiableNavigableSet(final NavigableSet set) { + if (set instanceof Unmodifiable) { + return set; + } + return new UnmodifiableNavigableSet(set); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param set the set to decorate, must not be null + * @throws NullPointerException if set is null + */ + private UnmodifiableNavigableSet(final NavigableSet set) { + super(set); + } + + //----------------------------------------------------------------------- + @Override + public Iterator iterator() { + return UnmodifiableIterator.unmodifiableIterator(decorated().iterator()); + } + + @Override + public boolean add(final E object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + // SortedSet + //----------------------------------------------------------------------- + @Override + public SortedSet subSet(final E fromElement, final E toElement) { + final SortedSet sub = decorated().subSet(fromElement, toElement); + return UnmodifiableSortedSet.unmodifiableSortedSet(sub); + } + + @Override + public SortedSet headSet(final E toElement) { + final SortedSet head = decorated().headSet(toElement); + return UnmodifiableSortedSet.unmodifiableSortedSet(head); + } + + @Override + public SortedSet tailSet(final E fromElement) { + final SortedSet tail = decorated().tailSet(fromElement); + return UnmodifiableSortedSet.unmodifiableSortedSet(tail); + } + + // NavigableSet + //----------------------------------------------------------------------- + @Override + public NavigableSet descendingSet() { + return unmodifiableNavigableSet(decorated().descendingSet()); + } + + @Override + public Iterator descendingIterator() { + return UnmodifiableIterator.unmodifiableIterator(decorated().descendingIterator()); + } + + @Override + public NavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + final NavigableSet sub = decorated().subSet(fromElement, fromInclusive, toElement, toInclusive); + return unmodifiableNavigableSet(sub); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + final NavigableSet head = decorated().headSet(toElement, inclusive); + return unmodifiableNavigableSet(head); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + final NavigableSet tail = decorated().tailSet(fromElement, inclusive); + return unmodifiableNavigableSet(tail); + } + + //----------------------------------------------------------------------- + /** + * Write the collection out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the collection in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + */ + @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setCollection((Collection) in.readObject()); // (1) + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableSet.java new file mode 100644 index 000000000..a81b3f2fc --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableSet.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; + +/** + * Decorates another Set to ensure it can't be altered. + *

              + * This class is Serializable from Commons Collections 3.1. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableSet + extends AbstractSerializableSetDecorator + implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = 6499119872185240161L; + + /** + * Factory method to create an unmodifiable set. + * + * @param the element type + * @param set the set to decorate, must not be null + * @return a new unmodifiable set + * @throws NullPointerException if set is null + * @since 4.0 + */ + public static Set unmodifiableSet(final Set set) { + if (set instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final Set tmpSet = (Set) set; + return tmpSet; + } + return new UnmodifiableSet(set); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param set the set to decorate, must not be null + * @throws NullPointerException if set is null + */ + @SuppressWarnings("unchecked") // safe to upcast + private UnmodifiableSet(final Set set) { + super((Set) set); + } + + //----------------------------------------------------------------------- + @Override + public Iterator iterator() { + return UnmodifiableIterator.unmodifiableIterator(decorated().iterator()); + } + + @Override + public boolean add(final E object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableSortedSet.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableSortedSet.java new file mode 100644 index 000000000..6e600c8cf --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/UnmodifiableSortedSet.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.set; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.SortedSet; + +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; + +/** + * Decorates another SortedSet to ensure it can't be altered. + *

              + * This class is Serializable from Commons Collections 3.1. + *

              + * Attempts to modify it will result in an UnsupportedOperationException. + * + * @since 3.0 + * @version $Id: UnmodifiableSortedSet.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public final class UnmodifiableSortedSet + extends AbstractSortedSetDecorator + implements Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = -725356885467962424L; + + /** + * Factory method to create an unmodifiable set. + * + * @param the element type + * @param set the set to decorate, must not be null + * @return a new unmodifiable {@link SortedSet} + * @throws NullPointerException if set is null + * @since 4.0 + */ + public static SortedSet unmodifiableSortedSet(final SortedSet set) { + if (set instanceof Unmodifiable) { + return set; + } + return new UnmodifiableSortedSet(set); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param set the set to decorate, must not be null + * @throws NullPointerException if set is null + */ + private UnmodifiableSortedSet(final SortedSet set) { + super(set); + } + + //----------------------------------------------------------------------- + @Override + public Iterator iterator() { + return UnmodifiableIterator.unmodifiableIterator(decorated().iterator()); + } + + @Override + public boolean add(final E object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + @Override + public SortedSet subSet(final E fromElement, final E toElement) { + final SortedSet sub = decorated().subSet(fromElement, toElement); + return unmodifiableSortedSet(sub); + } + + @Override + public SortedSet headSet(final E toElement) { + final SortedSet head = decorated().headSet(toElement); + return unmodifiableSortedSet(head); + } + + @Override + public SortedSet tailSet(final E fromElement) { + final SortedSet tail = decorated().tailSet(fromElement); + return unmodifiableSortedSet(tail); + } + + //----------------------------------------------------------------------- + /** + * Write the collection out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the collection in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + */ + @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setCollection((Collection) in.readObject()); // (1) + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/set/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/set/package-info.java new file mode 100644 index 000000000..de4d446ac --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/set/package-info.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the {@link java.util.Set Set}, + * {@link java.util.SortedSet SortedSet} and + * {@link java.util.NavigableSet NavigableSet} interfaces. + *

              + * The implementations are in the form of direct implementations and decorators. + * A decorator wraps another implementation of the interface to add some + * specific additional functionality. + *

              + * The following implementations are provided in the package: + *

                + *
              • CompositeSet - a set that combines multiple sets into one + *
              + * The following decorators are provided in the package: + *
                + *
              • Unmodifiable - ensures the collection cannot be altered + *
              • Predicated - ensures that only elements that are valid according to a predicate can be added + *
              • Transformed - transforms each element added + *
              • ListOrdered - ensures that insertion order is retained + *
              • MapBackedSet - a set formed by decorating a Map + *
              + * + * @version $Id: package-info.java 1682768 2015-05-31 18:35:18Z tn $ + */ +package org.apache.commons.collections4.set; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/splitmap/AbstractIterableGetMapDecorator.java b/fine-commons-collections4/src/org/apache/commons/collections4/splitmap/AbstractIterableGetMapDecorator.java new file mode 100644 index 000000000..12d216efa --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/splitmap/AbstractIterableGetMapDecorator.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.splitmap; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.IterableGet; +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.map.EntrySetToMapIteratorAdapter; + +/** + * {@link IterableGet} that uses a {@link Map} for the + * {@link org.apache.commons.collections4.Get Get} implementation. + * + * @since 4.0 + * @version $Id: AbstractIterableGetMapDecorator.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class AbstractIterableGetMapDecorator implements IterableGet { + + /** The map to decorate */ + transient Map map; + + /** + * Create a new AbstractSplitMapDecorator. + * @param map the map to decorate, must not be null + * @throws NullPointerException if map is null + */ + public AbstractIterableGetMapDecorator(final Map map) { + if (map == null) { + throw new NullPointerException("Map must not be null."); + } + this.map = map; + } + + /** + * Constructor only used in deserialization, do not use otherwise. + */ + protected AbstractIterableGetMapDecorator() { + super(); + } + + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + protected Map decorated() { + return map; + } + + public boolean containsKey(final Object key) { + return decorated().containsKey(key); + } + + public boolean containsValue(final Object value) { + return decorated().containsValue(value); + } + + public Set> entrySet() { + return decorated().entrySet(); + } + + public V get(final Object key) { + return decorated().get(key); + } + + public V remove(final Object key) { + return decorated().remove(key); + } + + public boolean isEmpty() { + return decorated().isEmpty(); + } + + public Set keySet() { + return decorated().keySet(); + } + + public int size() { + return decorated().size(); + } + + public Collection values() { + return decorated().values(); + } + + /** + * Get a MapIterator over this Get. + * @return MapIterator + */ + public MapIterator mapIterator() { + return new EntrySetToMapIteratorAdapter(entrySet()); + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + return decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + + @Override + public String toString() { + return decorated().toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/splitmap/TransformedSplitMap.java b/fine-commons-collections4/src/org/apache/commons/collections4/splitmap/TransformedSplitMap.java new file mode 100644 index 000000000..a5a43683a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/splitmap/TransformedSplitMap.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.splitmap; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Map; + +import org.apache.commons.collections4.Put; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.map.LinkedMap; + +/** + * Decorates another {@link Map} to transform objects that are added. + *

              + * The Map put methods and Map.Entry setValue method are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must use + * the Integer form to remove objects. + *

              + * Note that TransformedMap is not synchronized and is not + * thread-safe. If you wish to use this map from multiple threads + * concurrently, you must use appropriate synchronization. The simplest approach + * is to wrap this map using {@link java.util.Collections#synchronizedMap(Map)}. + * This class may throw exceptions when accessed by concurrent threads without + * synchronization. + *

              + * The "put" and "get" type constraints of this class are mutually independent; + * contrast with {@link org.apache.commons.collections4.map.TransformedMap} which, + * by virtue of its implementing {@link Map}<K, V>, must be constructed in such + * a way that its read and write parameters are generalized to a common (super-)type. + * In practice this would often mean >Object, Object>, defeating + * much of the usefulness of having parameterized types. + *

              + * On the downside, this class is not drop-in compatible with {@link java.util.Map} + * but is intended to be worked with either directly or by {@link Put} and + * {@link org.apache.commons.collections4.Get Get} generalizations. + * + * @since 4.0 + * @version $Id: TransformedSplitMap.java 1686855 2015-06-22 13:00:27Z tn $ + * + * @see org.apache.commons.collections4.SplitMapUtils#readableMap(org.apache.commons.collections4.Get) + * @see org.apache.commons.collections4.SplitMapUtils#writableMap(Put) + */ +public class TransformedSplitMap extends AbstractIterableGetMapDecorator + implements Put, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 5966875321133456994L; + + /** The transformer to use for the key */ + private final Transformer keyTransformer; + /** The transformer to use for the value */ + private final Transformer valueTransformer; + + /** + * Factory method to create a transforming map. + *

              + * If there are any elements already in the map being decorated, they are + * NOT transformed. + * + * @param the input key type + * @param the output key type + * @param the input value type + * @param the output value type + * @param map the map to decorate, must not be null + * @param keyTransformer the transformer to use for key conversion, must not be null + * @param valueTransformer the transformer to use for value conversion, must not be null + * @return a new transformed map + * @throws NullPointerException if map or either of the transformers is null + */ + public static TransformedSplitMap transformingMap(final Map map, + final Transformer keyTransformer, + final Transformer valueTransformer) { + return new TransformedSplitMap(map, keyTransformer, valueTransformer); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + *

              + * If there are any elements already in the collection being decorated, they + * are NOT transformed. + * + * @param map the map to decorate, must not be null + * @param keyTransformer the transformer to use for key conversion, must not be null + * @param valueTransformer the transformer to use for value conversion, must not be null + * @throws NullPointerException if map or either of the transformers is null + */ + protected TransformedSplitMap(final Map map, final Transformer keyTransformer, + final Transformer valueTransformer) { + super(map); + if (keyTransformer == null) { + throw new NullPointerException("KeyTransformer must not be null."); + } + this.keyTransformer = keyTransformer; + if (valueTransformer == null) { + throw new NullPointerException("ValueTransformer must not be null."); + } + this.valueTransformer = valueTransformer; + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated()); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since 3.1 + */ + @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + map = (Map) in.readObject(); // (1) + } + + //----------------------------------------------------------------------- + /** + * Transforms a key. + *

              + * The transformer itself may throw an exception if necessary. + * + * @param object the object to transform + * @return the transformed object + */ + protected K transformKey(final J object) { + return keyTransformer.transform(object); + } + + /** + * Transforms a value. + *

              + * The transformer itself may throw an exception if necessary. + * + * @param object the object to transform + * @return the transformed object + */ + protected V transformValue(final U object) { + return valueTransformer.transform(object); + } + + /** + * Transforms a map. + *

              + * The transformer itself may throw an exception if necessary. + * + * @param map the map to transform + * @return the transformed object + */ + @SuppressWarnings("unchecked") + protected Map transformMap(final Map map) { + if (map.isEmpty()) { + return (Map) map; + } + final Map result = new LinkedMap(map.size()); + + for (final Map.Entry entry : map.entrySet()) { + result.put(transformKey(entry.getKey()), transformValue(entry.getValue())); + } + return result; + } + + /** + * Override to transform the value when using setValue. + * + * @param value the value to transform + * @return the transformed value + */ + protected V checkSetValue(final U value) { + return valueTransformer.transform(value); + } + + //----------------------------------------------------------------------- + public V put(final J key, final U value) { + return decorated().put(transformKey(key), transformValue(value)); + } + + public void putAll(final Map mapToCopy) { + decorated().putAll(transformMap(mapToCopy)); + } + + public void clear() { + decorated().clear(); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/splitmap/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/splitmap/package-info.java new file mode 100644 index 000000000..405be61dc --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/splitmap/package-info.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * The "split map" concept is that of an object that implements + * the {@link org.apache.commons.collections4.Put Put} and + * {@link org.apache.commons.collections4.Get Get} interfaces, + * with differing generic types. This is like a pre-generics + * {@link java.util.Map Map} whose input key/value constraints are + * different than its output key/value constraints. While it would + * be possible to declare a "split map" with matching input/output + * key/value constraints, this would be a {@link java.util.Map Map} + * and would therefore make little sense (any Commons Collections + * {@link java.util.Map Map} implementation will also implement + * {@link org.apache.commons.collections4.Put Put} and + * {@link org.apache.commons.collections4.Get Get} with matching + * generic parameters). + *

              + * The following decorators are provided: + *

                + *
              • Transformed - transforms each element added + *
              + * + * @version $Id: package-info.java 1469004 2013-04-17 17:37:03Z tn $ + */ +package org.apache.commons.collections4.splitmap; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/trie/AbstractBitwiseTrie.java b/fine-commons-collections4/src/org/apache/commons/collections4/trie/AbstractBitwiseTrie.java new file mode 100644 index 000000000..557590c52 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/trie/AbstractBitwiseTrie.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.trie; + +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.collections4.Trie; + +/** + * This class provides some basic {@link Trie} functionality and + * utility methods for actual bitwise {@link Trie} implementations. + * + * @since 4.0 + * @version $Id: AbstractBitwiseTrie.java 1492866 2013-06-13 21:01:00Z tn $ + */ +public abstract class AbstractBitwiseTrie extends AbstractMap + implements Trie, Serializable { + + private static final long serialVersionUID = 5826987063535505652L; + + /** + * The {@link KeyAnalyzer} that's being used to build the PATRICIA {@link Trie}. + */ + private final KeyAnalyzer keyAnalyzer; + + /** + * Constructs a new {@link Trie} using the given {@link KeyAnalyzer}. + * + * @param keyAnalyzer the {@link KeyAnalyzer} to use + */ + protected AbstractBitwiseTrie(final KeyAnalyzer keyAnalyzer) { + if (keyAnalyzer == null) { + throw new NullPointerException("keyAnalyzer"); + } + + this.keyAnalyzer = keyAnalyzer; + } + + /** + * Returns the {@link KeyAnalyzer} that constructed the {@link Trie}. + * @return the {@link KeyAnalyzer} used by this {@link Trie} + */ + protected KeyAnalyzer getKeyAnalyzer() { + return keyAnalyzer; + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(); + buffer.append("Trie[").append(size()).append("]={\n"); + for (final Map.Entry entry : entrySet()) { + buffer.append(" ").append(entry).append("\n"); + } + buffer.append("}\n"); + return buffer.toString(); + } + + /** + * A utility method to cast keys. It actually doesn't cast anything. It's just fooling the compiler! + */ + @SuppressWarnings("unchecked") + final K castKey(final Object key) { + return (K) key; + } + + /** + * Returns the length of the given key in bits + * + * @see KeyAnalyzer#lengthInBits(Object) + */ + final int lengthInBits(final K key) { + if (key == null) { + return 0; + } + + return keyAnalyzer.lengthInBits(key); + } + + /** + * Returns the number of bits per element in the key + * + * @see KeyAnalyzer#bitsPerElement() + */ + final int bitsPerElement() { + return keyAnalyzer.bitsPerElement(); + } + + /** + * Returns whether or not the given bit on the key is set or false if the key is null. + * + * @see KeyAnalyzer#isBitSet(Object, int, int) + */ + final boolean isBitSet(final K key, final int bitIndex, final int lengthInBits) { + if (key == null) { // root's might be null! + return false; + } + return keyAnalyzer.isBitSet(key, bitIndex, lengthInBits); + } + + /** + * Utility method for calling {@link KeyAnalyzer#bitIndex(Object, int, int, Object, int, int)}. + */ + final int bitIndex(final K key, final K foundKey) { + return keyAnalyzer.bitIndex(key, 0, lengthInBits(key), foundKey, 0, lengthInBits(foundKey)); + } + + /** + * An utility method for calling {@link KeyAnalyzer#compare(Object, Object)} + */ + final boolean compareKeys(final K key, final K other) { + if (key == null) { + return other == null; + } else if (other == null) { + return false; + } + + return keyAnalyzer.compare(key, other) == 0; + } + + /** + * Returns true if both values are either null or equal. + */ + static boolean compare(final Object a, final Object b) { + return a == null ? b == null : a.equals(b); + } + + /** + * A basic implementation of {@link Entry}. + */ + abstract static class BasicEntry implements Map.Entry, Serializable { + + private static final long serialVersionUID = -944364551314110330L; + + protected K key; + + protected V value; + + public BasicEntry(final K key) { + this.key = key; + } + + public BasicEntry(final K key, final V value) { + this.key = key; + this.value = value; + } + + /** + * Replaces the current key and value with the provided key & value. + */ + public V setKeyValue(final K key, final V value) { + this.key = key; + return setValue(value); + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(final V value) { + final V previous = this.value; + this.value = value; + return previous; + } + + @Override + public int hashCode() { + return (getKey() == null ? 0 : getKey().hashCode()) ^ + (getValue() == null ? 0 : getValue().hashCode()); + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } else if (!(o instanceof Map.Entry)) { + return false; + } + + final Map.Entry other = (Map.Entry)o; + if (compare(key, other.getKey()) + && compare(value, other.getValue())) { + return true; + } + return false; + } + + @Override + public String toString() { + return key + "=" + value; + } + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/trie/AbstractPatriciaTrie.java b/fine-commons-collections4/src/org/apache/commons/collections4/trie/AbstractPatriciaTrie.java new file mode 100644 index 000000000..56691faf4 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/trie/AbstractPatriciaTrie.java @@ -0,0 +1,2406 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.trie; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; + +import org.apache.commons.collections4.OrderedMapIterator; + +/** + * This class implements the base PATRICIA algorithm and everything that + * is related to the {@link Map} interface. + * + * @since 4.0 + * @version $Id: AbstractPatriciaTrie.java 1648941 2015-01-01 21:10:46Z tn $ + */ +abstract class AbstractPatriciaTrie extends AbstractBitwiseTrie { + + private static final long serialVersionUID = 5155253417231339498L; + + /** The root node of the {@link Trie}. */ + private transient TrieEntry root = new TrieEntry(null, null, -1); + + /** + * Each of these fields are initialized to contain an instance of the + * appropriate view the first time this view is requested. The views are + * stateless, so there's no reason to create more than one of each. + */ + private transient volatile Set keySet; + private transient volatile Collection values; + private transient volatile Set> entrySet; + + /** The current size of the {@link Trie}. */ + private transient int size = 0; + + /** + * The number of times this {@link Trie} has been modified. + * It's used to detect concurrent modifications and fail-fast the {@link Iterator}s. + */ + protected transient int modCount = 0; + + protected AbstractPatriciaTrie(final KeyAnalyzer keyAnalyzer) { + super(keyAnalyzer); + } + + /** + * Constructs a new {@link org.apache.commons.collections4.Trie Trie} using the given + * {@link KeyAnalyzer} and initializes the {@link org.apache.commons.collections4.Trie Trie} + * with the values from the provided {@link Map}. + */ + protected AbstractPatriciaTrie(final KeyAnalyzer keyAnalyzer, + final Map map) { + super(keyAnalyzer); + putAll(map); + } + + //----------------------------------------------------------------------- + @Override + public void clear() { + root.key = null; + root.bitIndex = -1; + root.value = null; + + root.parent = null; + root.left = root; + root.right = null; + root.predecessor = root; + + size = 0; + incrementModCount(); + } + + @Override + public int size() { + return size; + } + + /** + * A helper method to increment the {@link Trie} size and the modification counter. + */ + void incrementSize() { + size++; + incrementModCount(); + } + + /** + * A helper method to decrement the {@link Trie} size and increment the modification counter. + */ + void decrementSize() { + size--; + incrementModCount(); + } + + /** + * A helper method to increment the modification counter. + */ + private void incrementModCount() { + ++modCount; + } + + @Override + public V put(final K key, final V value) { + if (key == null) { + throw new NullPointerException("Key cannot be null"); + } + + final int lengthInBits = lengthInBits(key); + + // The only place to store a key with a length + // of zero bits is the root node + if (lengthInBits == 0) { + if (root.isEmpty()) { + incrementSize(); + } else { + incrementModCount(); + } + return root.setKeyValue(key, value); + } + + final TrieEntry found = getNearestEntryForKey(key, lengthInBits); + if (compareKeys(key, found.key)) { + if (found.isEmpty()) { // <- must be the root + incrementSize(); + } else { + incrementModCount(); + } + return found.setKeyValue(key, value); + } + + final int bitIndex = bitIndex(key, found.key); + if (!KeyAnalyzer.isOutOfBoundsIndex(bitIndex)) { + if (KeyAnalyzer.isValidBitIndex(bitIndex)) { // in 99.999...9% the case + /* NEW KEY+VALUE TUPLE */ + final TrieEntry t = new TrieEntry(key, value, bitIndex); + addEntry(t, lengthInBits); + incrementSize(); + return null; + } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { + // A bits of the Key are zero. The only place to + // store such a Key is the root Node! + + /* NULL BIT KEY */ + if (root.isEmpty()) { + incrementSize(); + } else { + incrementModCount(); + } + return root.setKeyValue(key, value); + + } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { + // This is a very special and rare case. + + /* REPLACE OLD KEY+VALUE */ + if (found != root) { + incrementModCount(); + return found.setKeyValue(key, value); + } + } + } + + throw new IllegalArgumentException("Failed to put: " + key + " -> " + value + ", " + bitIndex); + } + + /** + * Adds the given {@link TrieEntry} to the {@link Trie}. + */ + TrieEntry addEntry(final TrieEntry entry, final int lengthInBits) { + TrieEntry current = root.left; + TrieEntry path = root; + while(true) { + if (current.bitIndex >= entry.bitIndex + || current.bitIndex <= path.bitIndex) { + entry.predecessor = entry; + + if (!isBitSet(entry.key, entry.bitIndex, lengthInBits)) { + entry.left = entry; + entry.right = current; + } else { + entry.left = current; + entry.right = entry; + } + + entry.parent = path; + if (current.bitIndex >= entry.bitIndex) { + current.parent = entry; + } + + // if we inserted an uplink, set the predecessor on it + if (current.bitIndex <= path.bitIndex) { + current.predecessor = entry; + } + + if (path == root || !isBitSet(entry.key, path.bitIndex, lengthInBits)) { + path.left = entry; + } else { + path.right = entry; + } + + return entry; + } + + path = current; + + if (!isBitSet(entry.key, current.bitIndex, lengthInBits)) { + current = current.left; + } else { + current = current.right; + } + } + } + + @Override + public V get(final Object k) { + final TrieEntry entry = getEntry(k); + return entry != null ? entry.getValue() : null; + } + + /** + * Returns the entry associated with the specified key in the + * PatriciaTrieBase. Returns null if the map contains no mapping + * for this key. + *

              + * This may throw ClassCastException if the object is not of type K. + */ + TrieEntry getEntry(final Object k) { + final K key = castKey(k); + if (key == null) { + return null; + } + + final int lengthInBits = lengthInBits(key); + final TrieEntry entry = getNearestEntryForKey(key, lengthInBits); + return !entry.isEmpty() && compareKeys(key, entry.key) ? entry : null; + } + + /** + * Returns the {@link Entry} whose key is closest in a bitwise XOR + * metric to the given key. This is NOT lexicographic closeness. + * For example, given the keys: + * + *

                + *
              1. D = 1000100 + *
              2. H = 1001000 + *
              3. L = 1001100 + *
              + * + * If the {@link Trie} contained 'H' and 'L', a lookup of 'D' would + * return 'L', because the XOR distance between D & L is smaller + * than the XOR distance between D & H. + * + * @param key the key to use in the search + * @return the {@link Entry} whose key is closest in a bitwise XOR metric + * to the provided key + */ + public Map.Entry select(final K key) { + final int lengthInBits = lengthInBits(key); + final Reference> reference = new Reference>(); + if (!selectR(root.left, -1, key, lengthInBits, reference)) { + return reference.get(); + } + return null; + } + + /** + * Returns the key that is closest in a bitwise XOR metric to the + * provided key. This is NOT lexicographic closeness! + * + * For example, given the keys: + * + *
                + *
              1. D = 1000100 + *
              2. H = 1001000 + *
              3. L = 1001100 + *
              + * + * If the {@link Trie} contained 'H' and 'L', a lookup of 'D' would + * return 'L', because the XOR distance between D & L is smaller + * than the XOR distance between D & H. + * + * @param key the key to use in the search + * @return the key that is closest in a bitwise XOR metric to the provided key + */ + public K selectKey(final K key) { + final Map.Entry entry = select(key); + if (entry == null) { + return null; + } + return entry.getKey(); + } + + /** + * Returns the value whose key is closest in a bitwise XOR metric to + * the provided key. This is NOT lexicographic closeness! + * + * For example, given the keys: + * + *
                + *
              1. D = 1000100 + *
              2. H = 1001000 + *
              3. L = 1001100 + *
              + * + * If the {@link Trie} contained 'H' and 'L', a lookup of 'D' would + * return 'L', because the XOR distance between D & L is smaller + * than the XOR distance between D & H. + * + * @param key the key to use in the search + * @return the value whose key is closest in a bitwise XOR metric + * to the provided key + */ + public V selectValue(final K key) { + final Map.Entry entry = select(key); + if (entry == null) { + return null; + } + return entry.getValue(); + } + + /** + * This is equivalent to the other {@link #selectR(TrieEntry, int, Object, int, Cursor, Reference)} + * method but without its overhead because we're selecting only one best matching Entry from the {@link Trie}. + */ + private boolean selectR(final TrieEntry h, final int bitIndex, + final K key, final int lengthInBits, + final Reference> reference) { + + if (h.bitIndex <= bitIndex) { + // If we hit the root Node and it is empty + // we have to look for an alternative best + // matching node. + if (!h.isEmpty()) { + reference.set(h); + return false; + } + return true; + } + + if (!isBitSet(key, h.bitIndex, lengthInBits)) { + if (selectR(h.left, h.bitIndex, key, lengthInBits, reference)) { + return selectR(h.right, h.bitIndex, key, lengthInBits, reference); + } + } else { + if (selectR(h.right, h.bitIndex, key, lengthInBits, reference)) { + return selectR(h.left, h.bitIndex, key, lengthInBits, reference); + } + } + return false; + } + + @Override + public boolean containsKey(final Object k) { + if (k == null) { + return false; + } + + final K key = castKey(k); + final int lengthInBits = lengthInBits(key); + final TrieEntry entry = getNearestEntryForKey(key, lengthInBits); + return !entry.isEmpty() && compareKeys(key, entry.key); + } + + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = new EntrySet(); + } + return entrySet; + } + + @Override + public Set keySet() { + if (keySet == null) { + keySet = new KeySet(); + } + return keySet; + } + + @Override + public Collection values() { + if (values == null) { + values = new Values(); + } + return values; + } + + /** + * {@inheritDoc} + * + * @throws ClassCastException if provided key is of an incompatible type + */ + @Override + public V remove(final Object k) { + if (k == null) { + return null; + } + + final K key = castKey(k); + final int lengthInBits = lengthInBits(key); + TrieEntry current = root.left; + TrieEntry path = root; + while (true) { + if (current.bitIndex <= path.bitIndex) { + if (!current.isEmpty() && compareKeys(key, current.key)) { + return removeEntry(current); + } + return null; + } + + path = current; + + if (!isBitSet(key, current.bitIndex, lengthInBits)) { + current = current.left; + } else { + current = current.right; + } + } + } + + /** + * Returns the nearest entry for a given key. This is useful + * for finding knowing if a given key exists (and finding the value + * for it), or for inserting the key. + * + * The actual get implementation. This is very similar to + * selectR but with the exception that it might return the + * root Entry even if it's empty. + */ + TrieEntry getNearestEntryForKey(final K key, final int lengthInBits) { + TrieEntry current = root.left; + TrieEntry path = root; + while(true) { + if (current.bitIndex <= path.bitIndex) { + return current; + } + + path = current; + if (!isBitSet(key, current.bitIndex, lengthInBits)) { + current = current.left; + } else { + current = current.right; + } + } + } + + /** + * Removes a single entry from the {@link Trie}. + * + * If we found a Key (Entry h) then figure out if it's + * an internal (hard to remove) or external Entry (easy + * to remove) + */ + V removeEntry(final TrieEntry h) { + if (h != root) { + if (h.isInternalNode()) { + removeInternalEntry(h); + } else { + removeExternalEntry(h); + } + } + + decrementSize(); + return h.setKeyValue(null, null); + } + + /** + * Removes an external entry from the {@link Trie}. + * + * If it's an external Entry then just remove it. + * This is very easy and straight forward. + */ + private void removeExternalEntry(final TrieEntry h) { + if (h == root) { + throw new IllegalArgumentException("Cannot delete root Entry!"); + } else if (!h.isExternalNode()) { + throw new IllegalArgumentException(h + " is not an external Entry!"); + } + + final TrieEntry parent = h.parent; + final TrieEntry child = h.left == h ? h.right : h.left; + + if (parent.left == h) { + parent.left = child; + } else { + parent.right = child; + } + + // either the parent is changing, or the predecessor is changing. + if (child.bitIndex > parent.bitIndex) { + child.parent = parent; + } else { + child.predecessor = parent; + } + + } + + /** + * Removes an internal entry from the {@link Trie}. + * + * If it's an internal Entry then "good luck" with understanding + * this code. The Idea is essentially that Entry p takes Entry h's + * place in the trie which requires some re-wiring. + */ + private void removeInternalEntry(final TrieEntry h) { + if (h == root) { + throw new IllegalArgumentException("Cannot delete root Entry!"); + } else if (!h.isInternalNode()) { + throw new IllegalArgumentException(h + " is not an internal Entry!"); + } + + final TrieEntry p = h.predecessor; + + // Set P's bitIndex + p.bitIndex = h.bitIndex; + + // Fix P's parent, predecessor and child Nodes + { + final TrieEntry parent = p.parent; + final TrieEntry child = p.left == h ? p.right : p.left; + + // if it was looping to itself previously, + // it will now be pointed from it's parent + // (if we aren't removing it's parent -- + // in that case, it remains looping to itself). + // otherwise, it will continue to have the same + // predecessor. + if (p.predecessor == p && p.parent != h) { + p.predecessor = p.parent; + } + + if (parent.left == p) { + parent.left = child; + } else { + parent.right = child; + } + + if (child.bitIndex > parent.bitIndex) { + child.parent = parent; + } + } + + // Fix H's parent and child Nodes + { + // If H is a parent of its left and right child + // then change them to P + if (h.left.parent == h) { + h.left.parent = p; + } + + if (h.right.parent == h) { + h.right.parent = p; + } + + // Change H's parent + if (h.parent.left == h) { + h.parent.left = p; + } else { + h.parent.right = p; + } + } + + // Copy the remaining fields from H to P + //p.bitIndex = h.bitIndex; + p.parent = h.parent; + p.left = h.left; + p.right = h.right; + + // Make sure that if h was pointing to any uplinks, + // p now points to them. + if (isValidUplink(p.left, p)) { + p.left.predecessor = p; + } + + if (isValidUplink(p.right, p)) { + p.right.predecessor = p; + } + } + + /** + * Returns the entry lexicographically after the given entry. + * If the given entry is null, returns the first node. + */ + TrieEntry nextEntry(final TrieEntry node) { + if (node == null) { + return firstEntry(); + } + return nextEntryImpl(node.predecessor, node, null); + } + + /** + * Scans for the next node, starting at the specified point, and using 'previous' + * as a hint that the last node we returned was 'previous' (so we know not to return + * it again). If 'tree' is non-null, this will limit the search to the given tree. + * + * The basic premise is that each iteration can follow the following steps: + * + * 1) Scan all the way to the left. + * a) If we already started from this node last time, proceed to Step 2. + * b) If a valid uplink is found, use it. + * c) If the result is an empty node (root not set), break the scan. + * d) If we already returned the left node, break the scan. + * + * 2) Check the right. + * a) If we already returned the right node, proceed to Step 3. + * b) If it is a valid uplink, use it. + * c) Do Step 1 from the right node. + * + * 3) Back up through the parents until we encounter find a parent + * that we're not the right child of. + * + * 4) If there's no right child of that parent, the iteration is finished. + * Otherwise continue to Step 5. + * + * 5) Check to see if the right child is a valid uplink. + * a) If we already returned that child, proceed to Step 6. + * Otherwise, use it. + * + * 6) If the right child of the parent is the parent itself, we've + * already found & returned the end of the Trie, so exit. + * + * 7) Do Step 1 on the parent's right child. + */ + TrieEntry nextEntryImpl(final TrieEntry start, + final TrieEntry previous, final TrieEntry tree) { + + TrieEntry current = start; + + // Only look at the left if this was a recursive or + // the first check, otherwise we know we've already looked + // at the left. + if (previous == null || start != previous.predecessor) { + while (!current.left.isEmpty()) { + // stop traversing if we've already + // returned the left of this node. + if (previous == current.left) { + break; + } + + if (isValidUplink(current.left, current)) { + return current.left; + } + + current = current.left; + } + } + + // If there's no data at all, exit. + if (current.isEmpty()) { + return null; + } + + // If we've already returned the left, + // and the immediate right is null, + // there's only one entry in the Trie + // which is stored at the root. + // + // / ("") <-- root + // \_/ \ + // null <-- 'current' + // + if (current.right == null) { + return null; + } + + // If nothing valid on the left, try the right. + if (previous != current.right) { + // See if it immediately is valid. + if (isValidUplink(current.right, current)) { + return current.right; + } + + // Must search on the right's side if it wasn't initially valid. + return nextEntryImpl(current.right, previous, tree); + } + + // Neither left nor right are valid, find the first parent + // whose child did not come from the right & traverse it. + while (current == current.parent.right) { + // If we're going to traverse to above the subtree, stop. + if (current == tree) { + return null; + } + + current = current.parent; + } + + // If we're on the top of the subtree, we can't go any higher. + if (current == tree) { + return null; + } + + // If there's no right, the parent must be root, so we're done. + if (current.parent.right == null) { + return null; + } + + // If the parent's right points to itself, we've found one. + if (previous != current.parent.right + && isValidUplink(current.parent.right, current.parent)) { + return current.parent.right; + } + + // If the parent's right is itself, there can't be any more nodes. + if (current.parent.right == current.parent) { + return null; + } + + // We need to traverse down the parent's right's path. + return nextEntryImpl(current.parent.right, previous, tree); + } + + /** + * Returns the first entry the {@link Trie} is storing. + *

              + * This is implemented by going always to the left until + * we encounter a valid uplink. That uplink is the first key. + */ + TrieEntry firstEntry() { + // if Trie is empty, no first node. + if (isEmpty()) { + return null; + } + + return followLeft(root); + } + + /** + * Goes left through the tree until it finds a valid node. + */ + TrieEntry followLeft(TrieEntry node) { + while(true) { + TrieEntry child = node.left; + // if we hit root and it didn't have a node, go right instead. + if (child.isEmpty()) { + child = node.right; + } + + if (child.bitIndex <= node.bitIndex) { + return child; + } + + node = child; + } + } + + //----------------------------------------------------------------------- + + public Comparator comparator() { + return getKeyAnalyzer(); + } + + public K firstKey() { + if (size() == 0) { + throw new NoSuchElementException(); + } + return firstEntry().getKey(); + } + + public K lastKey() { + final TrieEntry entry = lastEntry(); + if (entry != null) { + return entry.getKey(); + } + throw new NoSuchElementException(); + } + + public K nextKey(final K key) { + if (key == null) { + throw new NullPointerException(); + } + final TrieEntry entry = getEntry(key); + if (entry != null) { + final TrieEntry nextEntry = nextEntry(entry); + return nextEntry != null ? nextEntry.getKey() : null; + } + return null; + } + + public K previousKey(final K key) { + if (key == null) { + throw new NullPointerException(); + } + final TrieEntry entry = getEntry(key); + if (entry != null) { + final TrieEntry prevEntry = previousEntry(entry); + return prevEntry != null ? prevEntry.getKey() : null; + } + return null; + } + + public OrderedMapIterator mapIterator() { + return new TrieMapIterator(); + } + + public SortedMap prefixMap(final K key) { + return getPrefixMapByBits(key, 0, lengthInBits(key)); + } + + /** + * Returns a view of this {@link Trie} of all elements that are prefixed + * by the number of bits in the given Key. + *

              + * The view that this returns is optimized to have a very efficient + * {@link Iterator}. The {@link SortedMap#firstKey()}, + * {@link SortedMap#lastKey()} & {@link Map#size()} methods must + * iterate over all possible values in order to determine the results. + * This information is cached until the PATRICIA {@link Trie} changes. + * All other methods (except {@link Iterator}) must compare the given + * key to the prefix to ensure that it is within the range of the view. + * The {@link Iterator}'s remove method must also relocate the subtree + * that contains the prefixes if the entry holding the subtree is + * removed or changes. Changing the subtree takes O(K) time. + * + * @param key the key to use in the search + * @param offsetInBits the prefix offset + * @param lengthInBits the number of significant prefix bits + * @return a {@link SortedMap} view of this {@link Trie} with all elements whose + * key is prefixed by the search key + */ + private SortedMap getPrefixMapByBits(final K key, final int offsetInBits, final int lengthInBits) { + + final int offsetLength = offsetInBits + lengthInBits; + if (offsetLength > lengthInBits(key)) { + throw new IllegalArgumentException(offsetInBits + " + " + + lengthInBits + " > " + lengthInBits(key)); + } + + if (offsetLength == 0) { + return this; + } + + return new PrefixRangeMap(key, offsetInBits, lengthInBits); + } + + public SortedMap headMap(final K toKey) { + return new RangeEntryMap(null, toKey); + } + + public SortedMap subMap(final K fromKey, final K toKey) { + return new RangeEntryMap(fromKey, toKey); + } + + public SortedMap tailMap(final K fromKey) { + return new RangeEntryMap(fromKey, null); + } + + /** + * Returns an entry strictly higher than the given key, + * or null if no such entry exists. + */ + TrieEntry higherEntry(final K key) { + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + final int lengthInBits = lengthInBits(key); + + if (lengthInBits == 0) { + if (!root.isEmpty()) { + // If data in root, and more after -- return it. + if (size() > 1) { + return nextEntry(root); + } + // If no more after, no higher entry. + return null; + } + // Root is empty & we want something after empty, return first. + return firstEntry(); + } + + final TrieEntry found = getNearestEntryForKey(key, lengthInBits); + if (compareKeys(key, found.key)) { + return nextEntry(found); + } + + final int bitIndex = bitIndex(key, found.key); + if (KeyAnalyzer.isValidBitIndex(bitIndex)) { + final TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, lengthInBits); + incrementSize(); // must increment because remove will decrement + final TrieEntry ceil = nextEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return ceil; + } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { + if (!root.isEmpty()) { + return firstEntry(); + } else if (size() > 1) { + return nextEntry(firstEntry()); + } else { + return null; + } + } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { + return nextEntry(found); + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the least key greater + * than or equal to the given key, or null if there is no such key. + */ + TrieEntry ceilingEntry(final K key) { + // Basically: + // Follow the steps of adding an entry, but instead... + // + // - If we ever encounter a situation where we found an equal + // key, we return it immediately. + // + // - If we hit an empty root, return the first iterable item. + // + // - If we have to add a new item, we temporarily add it, + // find the successor to it, then remove the added item. + // + // These steps ensure that the returned value is either the + // entry for the key itself, or the first entry directly after + // the key. + + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + final int lengthInBits = lengthInBits(key); + + if (lengthInBits == 0) { + if (!root.isEmpty()) { + return root; + } + return firstEntry(); + } + + final TrieEntry found = getNearestEntryForKey(key, lengthInBits); + if (compareKeys(key, found.key)) { + return found; + } + + final int bitIndex = bitIndex(key, found.key); + if (KeyAnalyzer.isValidBitIndex(bitIndex)) { + final TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, lengthInBits); + incrementSize(); // must increment because remove will decrement + final TrieEntry ceil = nextEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return ceil; + } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { + if (!root.isEmpty()) { + return root; + } + return firstEntry(); + } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { + return found; + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the greatest key + * strictly less than the given key, or null if there is no such key. + */ + TrieEntry lowerEntry(final K key) { + // Basically: + // Follow the steps of adding an entry, but instead... + // + // - If we ever encounter a situation where we found an equal + // key, we return it's previousEntry immediately. + // + // - If we hit root (empty or not), return null. + // + // - If we have to add a new item, we temporarily add it, + // find the previousEntry to it, then remove the added item. + // + // These steps ensure that the returned value is always just before + // the key or null (if there was nothing before it). + + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + final int lengthInBits = lengthInBits(key); + + if (lengthInBits == 0) { + return null; // there can never be anything before root. + } + + final TrieEntry found = getNearestEntryForKey(key, lengthInBits); + if (compareKeys(key, found.key)) { + return previousEntry(found); + } + + final int bitIndex = bitIndex(key, found.key); + if (KeyAnalyzer.isValidBitIndex(bitIndex)) { + final TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, lengthInBits); + incrementSize(); // must increment because remove will decrement + final TrieEntry prior = previousEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return prior; + } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { + return null; + } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { + return previousEntry(found); + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the greatest key + * less than or equal to the given key, or null if there is no such key. + */ + TrieEntry floorEntry(final K key) { + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + final int lengthInBits = lengthInBits(key); + + if (lengthInBits == 0) { + if (!root.isEmpty()) { + return root; + } + return null; + } + + final TrieEntry found = getNearestEntryForKey(key, lengthInBits); + if (compareKeys(key, found.key)) { + return found; + } + + final int bitIndex = bitIndex(key, found.key); + if (KeyAnalyzer.isValidBitIndex(bitIndex)) { + final TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, lengthInBits); + incrementSize(); // must increment because remove will decrement + final TrieEntry floor = previousEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return floor; + } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { + if (!root.isEmpty()) { + return root; + } + return null; + } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { + return found; + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Finds the subtree that contains the prefix. + * + * This is very similar to getR but with the difference that + * we stop the lookup if h.bitIndex > lengthInBits. + */ + TrieEntry subtree(final K prefix, final int offsetInBits, final int lengthInBits) { + TrieEntry current = root.left; + TrieEntry path = root; + while(true) { + if (current.bitIndex <= path.bitIndex || lengthInBits <= current.bitIndex) { + break; + } + + path = current; + if (!isBitSet(prefix, offsetInBits + current.bitIndex, offsetInBits + lengthInBits)) { + current = current.left; + } else { + current = current.right; + } + } + + // Make sure the entry is valid for a subtree. + final TrieEntry entry = current.isEmpty() ? path : current; + + // If entry is root, it can't be empty. + if (entry.isEmpty()) { + return null; + } + + final int endIndexInBits = offsetInBits + lengthInBits; + + // if root && length of root is less than length of lookup, + // there's nothing. + // (this prevents returning the whole subtree if root has an empty + // string and we want to lookup things with "\0") + if (entry == root && lengthInBits(entry.getKey()) < endIndexInBits) { + return null; + } + + // Found key's length-th bit differs from our key + // which means it cannot be the prefix... + if (isBitSet(prefix, endIndexInBits - 1, endIndexInBits) + != isBitSet(entry.key, lengthInBits - 1, lengthInBits(entry.key))) { + return null; + } + + // ... or there are less than 'length' equal bits + final int bitIndex = getKeyAnalyzer().bitIndex(prefix, offsetInBits, lengthInBits, + entry.key, 0, lengthInBits(entry.getKey())); + + if (bitIndex >= 0 && bitIndex < lengthInBits) { + return null; + } + + return entry; + } + + /** + * Returns the last entry the {@link Trie} is storing. + * + *

              This is implemented by going always to the right until + * we encounter a valid uplink. That uplink is the last key. + */ + TrieEntry lastEntry() { + return followRight(root.left); + } + + /** + * Traverses down the right path until it finds an uplink. + */ + TrieEntry followRight(TrieEntry node) { + // if Trie is empty, no last entry. + if (node.right == null) { + return null; + } + + // Go as far right as possible, until we encounter an uplink. + while (node.right.bitIndex > node.bitIndex) { + node = node.right; + } + + return node.right; + } + + /** + * Returns the node lexicographically before the given node (or null if none). + * + * This follows four simple branches: + * - If the uplink that returned us was a right uplink: + * - If predecessor's left is a valid uplink from predecessor, return it. + * - Else, follow the right path from the predecessor's left. + * - If the uplink that returned us was a left uplink: + * - Loop back through parents until we encounter a node where + * node != node.parent.left. + * - If node.parent.left is uplink from node.parent: + * - If node.parent.left is not root, return it. + * - If it is root & root isEmpty, return null. + * - If it is root & root !isEmpty, return root. + * - If node.parent.left is not uplink from node.parent: + * - Follow right path for first right child from node.parent.left + * + * @param start the start entry + */ + TrieEntry previousEntry(final TrieEntry start) { + if (start.predecessor == null) { + throw new IllegalArgumentException("must have come from somewhere!"); + } + + if (start.predecessor.right == start) { + if (isValidUplink(start.predecessor.left, start.predecessor)) { + return start.predecessor.left; + } + return followRight(start.predecessor.left); + } + TrieEntry node = start.predecessor; + while (node.parent != null && node == node.parent.left) { + node = node.parent; + } + + if (node.parent == null) { // can be null if we're looking up root. + return null; + } + + if (isValidUplink(node.parent.left, node.parent)) { + if (node.parent.left == root) { + if (root.isEmpty()) { + return null; + } + return root; + + } + return node.parent.left; + } + return followRight(node.parent.left); + } + + /** + * Returns the entry lexicographically after the given entry. + * If the given entry is null, returns the first node. + * + * This will traverse only within the subtree. If the given node + * is not within the subtree, this will have undefined results. + */ + TrieEntry nextEntryInSubtree(final TrieEntry node, + final TrieEntry parentOfSubtree) { + if (node == null) { + return firstEntry(); + } + return nextEntryImpl(node.predecessor, node, parentOfSubtree); + } + + /** + * Returns true if 'next' is a valid uplink coming from 'from'. + */ + static boolean isValidUplink(final TrieEntry next, final TrieEntry from) { + return next != null && next.bitIndex <= from.bitIndex && !next.isEmpty(); + } + + /** + * A {@link Reference} allows us to return something through a Method's + * argument list. An alternative would be to an Array with a length of + * one (1) but that leads to compiler warnings. Computationally and memory + * wise there's no difference (except for the need to load the + * {@link Reference} Class but that happens only once). + */ + private static class Reference { + + private E item; + + public void set(final E item) { + this.item = item; + } + + public E get() { + return item; + } + } + + /** + * A {@link Trie} is a set of {@link TrieEntry} nodes. + */ + protected static class TrieEntry extends BasicEntry { + + private static final long serialVersionUID = 4596023148184140013L; + + /** The index this entry is comparing. */ + protected int bitIndex; + + /** The parent of this entry. */ + protected TrieEntry parent; + + /** The left child of this entry. */ + protected TrieEntry left; + + /** The right child of this entry. */ + protected TrieEntry right; + + /** The entry who uplinks to this entry. */ + protected TrieEntry predecessor; + + public TrieEntry(final K key, final V value, final int bitIndex) { + super(key, value); + + this.bitIndex = bitIndex; + + this.parent = null; + this.left = this; + this.right = null; + this.predecessor = this; + } + + /** + * Whether or not the entry is storing a key. + * Only the root can potentially be empty, all other + * nodes must have a key. + */ + public boolean isEmpty() { + return key == null; + } + + /** + * Neither the left nor right child is a loopback. + */ + public boolean isInternalNode() { + return left != this && right != this; + } + + /** + * Either the left or right child is a loopback. + */ + public boolean isExternalNode() { + return !isInternalNode(); + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(); + + if (bitIndex == -1) { + buffer.append("RootEntry("); + } else { + buffer.append("Entry("); + } + + buffer.append("key=").append(getKey()).append(" [").append(bitIndex).append("], "); + buffer.append("value=").append(getValue()).append(", "); + //buffer.append("bitIndex=").append(bitIndex).append(", "); + + if (parent != null) { + if (parent.bitIndex == -1) { + buffer.append("parent=").append("ROOT"); + } else { + buffer.append("parent=").append(parent.getKey()).append(" [").append(parent.bitIndex).append("]"); + } + } else { + buffer.append("parent=").append("null"); + } + buffer.append(", "); + + if (left != null) { + if (left.bitIndex == -1) { + buffer.append("left=").append("ROOT"); + } else { + buffer.append("left=").append(left.getKey()).append(" [").append(left.bitIndex).append("]"); + } + } else { + buffer.append("left=").append("null"); + } + buffer.append(", "); + + if (right != null) { + if (right.bitIndex == -1) { + buffer.append("right=").append("ROOT"); + } else { + buffer.append("right=").append(right.getKey()).append(" [").append(right.bitIndex).append("]"); + } + } else { + buffer.append("right=").append("null"); + } + buffer.append(", "); + + if (predecessor != null) { + if(predecessor.bitIndex == -1) { + buffer.append("predecessor=").append("ROOT"); + } else { + buffer.append("predecessor=").append(predecessor.getKey()).append(" ["). + append(predecessor.bitIndex).append("]"); + } + } + + buffer.append(")"); + return buffer.toString(); + } + } + + + /** + * This is a entry set view of the {@link Trie} as returned by {@link Map#entrySet()}. + */ + private class EntrySet extends AbstractSet> { + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(final Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + final TrieEntry candidate = getEntry(((Map.Entry)o).getKey()); + return candidate != null && candidate.equals(o); + } + + @Override + public boolean remove(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + if (contains(obj) == false) { + return false; + } + final Map.Entry entry = (Map.Entry) obj; + AbstractPatriciaTrie.this.remove(entry.getKey()); + return true; + } + + @Override + public int size() { + return AbstractPatriciaTrie.this.size(); + } + + @Override + public void clear() { + AbstractPatriciaTrie.this.clear(); + } + + /** + * An {@link Iterator} that returns {@link Entry} Objects. + */ + private class EntryIterator extends TrieIterator> { + public Map.Entry next() { + return nextEntry(); + } + } + } + + /** + * This is a key set view of the {@link Trie} as returned by {@link Map#keySet()}. + */ + private class KeySet extends AbstractSet { + + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public int size() { + return AbstractPatriciaTrie.this.size(); + } + + @Override + public boolean contains(final Object o) { + return containsKey(o); + } + + @Override + public boolean remove(final Object o) { + final int size = size(); + AbstractPatriciaTrie.this.remove(o); + return size != size(); + } + + @Override + public void clear() { + AbstractPatriciaTrie.this.clear(); + } + + /** + * An {@link Iterator} that returns Key Objects. + */ + private class KeyIterator extends TrieIterator { + public K next() { + return nextEntry().getKey(); + } + } + } + + /** + * This is a value view of the {@link Trie} as returned by {@link Map#values()}. + */ + private class Values extends AbstractCollection { + + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public int size() { + return AbstractPatriciaTrie.this.size(); + } + + @Override + public boolean contains(final Object o) { + return containsValue(o); + } + + @Override + public void clear() { + AbstractPatriciaTrie.this.clear(); + } + + @Override + public boolean remove(final Object o) { + for (final Iterator it = iterator(); it.hasNext(); ) { + final V value = it.next(); + if (compare(value, o)) { + it.remove(); + return true; + } + } + return false; + } + + /** + * An {@link Iterator} that returns Value Objects. + */ + private class ValueIterator extends TrieIterator { + public V next() { + return nextEntry().getValue(); + } + } + } + + /** + * An iterator for the entries. + */ + abstract class TrieIterator implements Iterator { + + /** For fast-fail. */ + protected int expectedModCount = AbstractPatriciaTrie.this.modCount; + + protected TrieEntry next; // the next node to return + protected TrieEntry current; // the current entry we're on + + /** + * Starts iteration from the root. + */ + protected TrieIterator() { + next = AbstractPatriciaTrie.this.nextEntry(null); + } + + /** + * Starts iteration at the given entry. + */ + protected TrieIterator(final TrieEntry firstEntry) { + next = firstEntry; + } + + /** + * Returns the next {@link TrieEntry}. + */ + protected TrieEntry nextEntry() { + if (expectedModCount != AbstractPatriciaTrie.this.modCount) { + throw new ConcurrentModificationException(); + } + + final TrieEntry e = next; + if (e == null) { + throw new NoSuchElementException(); + } + + next = findNext(e); + current = e; + return e; + } + + /** + * @see PatriciaTrie#nextEntry(TrieEntry) + */ + protected TrieEntry findNext(final TrieEntry prior) { + return AbstractPatriciaTrie.this.nextEntry(prior); + } + + public boolean hasNext() { + return next != null; + } + + public void remove() { + if (current == null) { + throw new IllegalStateException(); + } + + if (expectedModCount != AbstractPatriciaTrie.this.modCount) { + throw new ConcurrentModificationException(); + } + + final TrieEntry node = current; + current = null; + AbstractPatriciaTrie.this.removeEntry(node); + + expectedModCount = AbstractPatriciaTrie.this.modCount; + } + } + + /** + * An {@link OrderedMapIterator} for a {@link Trie}. + */ + private class TrieMapIterator extends TrieIterator implements OrderedMapIterator { + + protected TrieEntry previous; // the previous node to return + + public K next() { + return nextEntry().getKey(); + } + + public K getKey() { + if (current == null) { + throw new IllegalStateException(); + } + return current.getKey(); + } + + public V getValue() { + if (current == null) { + throw new IllegalStateException(); + } + return current.getValue(); + } + + public V setValue(final V value) { + if (current == null) { + throw new IllegalStateException(); + } + return current.setValue(value); + } + + public boolean hasPrevious() { + return previous != null; + } + + public K previous() { + return previousEntry().getKey(); + } + + @Override + protected TrieEntry nextEntry() { + final TrieEntry nextEntry = super.nextEntry(); + previous = nextEntry; + return nextEntry; + } + + protected TrieEntry previousEntry() { + if (expectedModCount != AbstractPatriciaTrie.this.modCount) { + throw new ConcurrentModificationException(); + } + + final TrieEntry e = previous; + if (e == null) { + throw new NoSuchElementException(); + } + + previous = AbstractPatriciaTrie.this.previousEntry(e); + next = current; + current = e; + return current; + } + + } + + /** + * A range view of the {@link Trie}. + */ + private abstract class RangeMap extends AbstractMap + implements SortedMap { + + /** The {@link #entrySet()} view. */ + private transient volatile Set> entrySet; + + /** + * Creates and returns an {@link #entrySet()} view of the {@link RangeMap}. + */ + protected abstract Set> createEntrySet(); + + /** + * Returns the FROM Key. + */ + protected abstract K getFromKey(); + + /** + * Whether or not the {@link #getFromKey()} is in the range. + */ + protected abstract boolean isFromInclusive(); + + /** + * Returns the TO Key. + */ + protected abstract K getToKey(); + + /** + * Whether or not the {@link #getToKey()} is in the range. + */ + protected abstract boolean isToInclusive(); + + public Comparator comparator() { + return AbstractPatriciaTrie.this.comparator(); + } + + @Override + public boolean containsKey(final Object key) { + if (!inRange(castKey(key))) { + return false; + } + + return AbstractPatriciaTrie.this.containsKey(key); + } + + @Override + public V remove(final Object key) { + if (!inRange(castKey(key))) { + return null; + } + + return AbstractPatriciaTrie.this.remove(key); + } + + @Override + public V get(final Object key) { + if (!inRange(castKey(key))) { + return null; + } + + return AbstractPatriciaTrie.this.get(key); + } + + @Override + public V put(final K key, final V value) { + if (!inRange(key)) { + throw new IllegalArgumentException("Key is out of range: " + key); + } + return AbstractPatriciaTrie.this.put(key, value); + } + + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = createEntrySet(); + } + return entrySet; + } + + public SortedMap subMap(final K fromKey, final K toKey) { + if (!inRange2(fromKey)) { + throw new IllegalArgumentException("FromKey is out of range: " + fromKey); + } + + if (!inRange2(toKey)) { + throw new IllegalArgumentException("ToKey is out of range: " + toKey); + } + + return createRangeMap(fromKey, isFromInclusive(), toKey, isToInclusive()); + } + + public SortedMap headMap(final K toKey) { + if (!inRange2(toKey)) { + throw new IllegalArgumentException("ToKey is out of range: " + toKey); + } + return createRangeMap(getFromKey(), isFromInclusive(), toKey, isToInclusive()); + } + + public SortedMap tailMap(final K fromKey) { + if (!inRange2(fromKey)) { + throw new IllegalArgumentException("FromKey is out of range: " + fromKey); + } + return createRangeMap(fromKey, isFromInclusive(), getToKey(), isToInclusive()); + } + + /** + * Returns true if the provided key is greater than TO and less than FROM. + */ + protected boolean inRange(final K key) { + final K fromKey = getFromKey(); + final K toKey = getToKey(); + + return (fromKey == null || inFromRange(key, false)) && (toKey == null || inToRange(key, false)); + } + + /** + * This form allows the high endpoint (as well as all legit keys). + */ + protected boolean inRange2(final K key) { + final K fromKey = getFromKey(); + final K toKey = getToKey(); + + return (fromKey == null || inFromRange(key, false)) && (toKey == null || inToRange(key, true)); + } + + /** + * Returns true if the provided key is in the FROM range of the {@link RangeMap}. + */ + protected boolean inFromRange(final K key, final boolean forceInclusive) { + final K fromKey = getFromKey(); + final boolean fromInclusive = isFromInclusive(); + + final int ret = getKeyAnalyzer().compare(key, fromKey); + if (fromInclusive || forceInclusive) { + return ret >= 0; + } + return ret > 0; + } + + /** + * Returns true if the provided key is in the TO range of the {@link RangeMap}. + */ + protected boolean inToRange(final K key, final boolean forceInclusive) { + final K toKey = getToKey(); + final boolean toInclusive = isToInclusive(); + + final int ret = getKeyAnalyzer().compare(key, toKey); + if (toInclusive || forceInclusive) { + return ret <= 0; + } + return ret < 0; + } + + /** + * Creates and returns a sub-range view of the current {@link RangeMap}. + */ + protected abstract SortedMap createRangeMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive); + } + + /** + * A {@link RangeMap} that deals with {@link Entry}s. + */ + private class RangeEntryMap extends RangeMap { + + /** The key to start from, null if the beginning. */ + private final K fromKey; + + /** The key to end at, null if till the end. */ + private final K toKey; + + /** Whether or not the 'from' is inclusive. */ + private final boolean fromInclusive; + + /** Whether or not the 'to' is inclusive. */ + private final boolean toInclusive; + + /** + * Creates a {@link RangeEntryMap} with the fromKey included and + * the toKey excluded from the range. + */ + protected RangeEntryMap(final K fromKey, final K toKey) { + this(fromKey, true, toKey, false); + } + + /** + * Creates a {@link RangeEntryMap}. + */ + protected RangeEntryMap(final K fromKey, final boolean fromInclusive, + final K toKey, final boolean toInclusive) { + + if (fromKey == null && toKey == null) { + throw new IllegalArgumentException("must have a from or to!"); + } + + if (fromKey != null && toKey != null && getKeyAnalyzer().compare(fromKey, toKey) > 0) { + throw new IllegalArgumentException("fromKey > toKey"); + } + + this.fromKey = fromKey; + this.fromInclusive = fromInclusive; + this.toKey = toKey; + this.toInclusive = toInclusive; + } + + public K firstKey() { + Map.Entry e = null; + if (fromKey == null) { + e = firstEntry(); + } else { + if (fromInclusive) { + e = ceilingEntry(fromKey); + } else { + e = higherEntry(fromKey); + } + } + + final K first = e != null ? e.getKey() : null; + if (e == null || toKey != null && !inToRange(first, false)) { + throw new NoSuchElementException(); + } + return first; + } + + public K lastKey() { + Map.Entry e; + if (toKey == null) { + e = lastEntry(); + } else { + if (toInclusive) { + e = floorEntry(toKey); + } else { + e = lowerEntry(toKey); + } + } + + final K last = e != null ? e.getKey() : null; + if (e == null || fromKey != null && !inFromRange(last, false)) { + throw new NoSuchElementException(); + } + return last; + } + + @Override + protected Set> createEntrySet() { + return new RangeEntrySet(this); + } + + @Override + public K getFromKey() { + return fromKey; + } + + @Override + public K getToKey() { + return toKey; + } + + @Override + public boolean isFromInclusive() { + return fromInclusive; + } + + @Override + public boolean isToInclusive() { + return toInclusive; + } + + @Override + protected SortedMap createRangeMap(final K fromKey, final boolean fromInclusive, + final K toKey, final boolean toInclusive) { + return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive); + } + } + + /** + * A {@link Set} view of a {@link RangeMap}. + */ + private class RangeEntrySet extends AbstractSet> { + + private final RangeMap delegate; + + private transient int size = -1; + + private transient int expectedModCount; + + /** + * Creates a {@link RangeEntrySet}. + */ + public RangeEntrySet(final RangeMap delegate) { + if (delegate == null) { + throw new NullPointerException("delegate"); + } + + this.delegate = delegate; + } + + @Override + public Iterator> iterator() { + final K fromKey = delegate.getFromKey(); + final K toKey = delegate.getToKey(); + + TrieEntry first = null; + if (fromKey == null) { + first = firstEntry(); + } else { + first = ceilingEntry(fromKey); + } + + TrieEntry last = null; + if (toKey != null) { + last = ceilingEntry(toKey); + } + + return new EntryIterator(first, last); + } + + @Override + public int size() { + if (size == -1 || expectedModCount != AbstractPatriciaTrie.this.modCount) { + size = 0; + + for (final Iterator it = iterator(); it.hasNext(); it.next()) { + ++size; + } + + expectedModCount = AbstractPatriciaTrie.this.modCount; + } + return size; + } + + @Override + public boolean isEmpty() { + return !iterator().hasNext(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean contains(final Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + final Map.Entry entry = (Map.Entry) o; + final K key = entry.getKey(); + if (!delegate.inRange(key)) { + return false; + } + + final TrieEntry node = getEntry(key); + return node != null && compare(node.getValue(), entry.getValue()); + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(final Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + final Map.Entry entry = (Map.Entry) o; + final K key = entry.getKey(); + if (!delegate.inRange(key)) { + return false; + } + + final TrieEntry node = getEntry(key); + if (node != null && compare(node.getValue(), entry.getValue())) { + removeEntry(node); + return true; + } + return false; + } + + /** + * An {@link Iterator} for {@link RangeEntrySet}s. + */ + private final class EntryIterator extends TrieIterator> { + + private final K excludedKey; + + /** + * Creates a {@link EntryIterator}. + */ + private EntryIterator(final TrieEntry first, final TrieEntry last) { + super(first); + this.excludedKey = last != null ? last.getKey() : null; + } + + @Override + public boolean hasNext() { + return next != null && !compare(next.key, excludedKey); + } + + public Map.Entry next() { + if (next == null || compare(next.key, excludedKey)) { + throw new NoSuchElementException(); + } + return nextEntry(); + } + } + } + + /** + * A submap used for prefix views over the {@link Trie}. + */ + private class PrefixRangeMap extends RangeMap { + + private final K prefix; + + private final int offsetInBits; + + private final int lengthInBits; + + private K fromKey = null; + + private K toKey = null; + + private transient int expectedModCount = 0; + + private int size = -1; + + /** + * Creates a {@link PrefixRangeMap}. + */ + private PrefixRangeMap(final K prefix, final int offsetInBits, final int lengthInBits) { + this.prefix = prefix; + this.offsetInBits = offsetInBits; + this.lengthInBits = lengthInBits; + } + + /** + * This method does two things. It determines the FROM + * and TO range of the {@link PrefixRangeMap} and the number + * of elements in the range. This method must be called every + * time the {@link Trie} has changed. + */ + private int fixup() { + // The trie has changed since we last found our toKey / fromKey + if (size == - 1 || AbstractPatriciaTrie.this.modCount != expectedModCount) { + final Iterator> it = super.entrySet().iterator(); + size = 0; + + Map.Entry entry = null; + if (it.hasNext()) { + entry = it.next(); + size = 1; + } + + fromKey = entry == null ? null : entry.getKey(); + if (fromKey != null) { + final TrieEntry prior = previousEntry((TrieEntry)entry); + fromKey = prior == null ? null : prior.getKey(); + } + + toKey = fromKey; + + while (it.hasNext()) { + ++size; + entry = it.next(); + } + + toKey = entry == null ? null : entry.getKey(); + + if (toKey != null) { + entry = nextEntry((TrieEntry)entry); + toKey = entry == null ? null : entry.getKey(); + } + + expectedModCount = AbstractPatriciaTrie.this.modCount; + } + + return size; + } + + public K firstKey() { + fixup(); + + Map.Entry e = null; + if (fromKey == null) { + e = firstEntry(); + } else { + e = higherEntry(fromKey); + } + + final K first = e != null ? e.getKey() : null; + if (e == null || !getKeyAnalyzer().isPrefix(prefix, offsetInBits, lengthInBits, first)) { + throw new NoSuchElementException(); + } + + return first; + } + + public K lastKey() { + fixup(); + + Map.Entry e = null; + if (toKey == null) { + e = lastEntry(); + } else { + e = lowerEntry(toKey); + } + + final K last = e != null ? e.getKey() : null; + if (e == null || !getKeyAnalyzer().isPrefix(prefix, offsetInBits, lengthInBits, last)) { + throw new NoSuchElementException(); + } + + return last; + } + + /** + * Returns true if this {@link PrefixRangeMap}'s key is a prefix of the provided key. + */ + @Override + protected boolean inRange(final K key) { + return getKeyAnalyzer().isPrefix(prefix, offsetInBits, lengthInBits, key); + } + + /** + * Same as {@link #inRange(Object)}. + */ + @Override + protected boolean inRange2(final K key) { + return inRange(key); + } + + /** + * Returns true if the provided Key is in the FROM range of the {@link PrefixRangeMap}. + */ + @Override + protected boolean inFromRange(final K key, final boolean forceInclusive) { + return getKeyAnalyzer().isPrefix(prefix, offsetInBits, lengthInBits, key); + } + + /** + * Returns true if the provided Key is in the TO range of the {@link PrefixRangeMap}. + */ + @Override + protected boolean inToRange(final K key, final boolean forceInclusive) { + return getKeyAnalyzer().isPrefix(prefix, offsetInBits, lengthInBits, key); + } + + @Override + protected Set> createEntrySet() { + return new PrefixRangeEntrySet(this); + } + + @Override + public K getFromKey() { + return fromKey; + } + + @Override + public K getToKey() { + return toKey; + } + + @Override + public boolean isFromInclusive() { + return false; + } + + @Override + public boolean isToInclusive() { + return false; + } + + @Override + protected SortedMap createRangeMap(final K fromKey, final boolean fromInclusive, + final K toKey, final boolean toInclusive) { + return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive); + } + } + + /** + * A prefix {@link RangeEntrySet} view of the {@link Trie}. + */ + private final class PrefixRangeEntrySet extends RangeEntrySet { + + private final PrefixRangeMap delegate; + + private TrieEntry prefixStart; + + private int expectedModCount = 0; + + /** + * Creates a {@link PrefixRangeEntrySet}. + */ + public PrefixRangeEntrySet(final PrefixRangeMap delegate) { + super(delegate); + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.fixup(); + } + + @Override + public Iterator> iterator() { + if (AbstractPatriciaTrie.this.modCount != expectedModCount) { + prefixStart = subtree(delegate.prefix, delegate.offsetInBits, delegate.lengthInBits); + expectedModCount = AbstractPatriciaTrie.this.modCount; + } + + if (prefixStart == null) { + final Set> empty = Collections.emptySet(); + return empty.iterator(); + } else if (delegate.lengthInBits > prefixStart.bitIndex) { + return new SingletonIterator(prefixStart); + } else { + return new EntryIterator(prefixStart, delegate.prefix, delegate.offsetInBits, delegate.lengthInBits); + } + } + + /** + * An {@link Iterator} that holds a single {@link TrieEntry}. + */ + private final class SingletonIterator implements Iterator> { + + private final TrieEntry entry; + + private int hit = 0; + + public SingletonIterator(final TrieEntry entry) { + this.entry = entry; + } + + public boolean hasNext() { + return hit == 0; + } + + public Map.Entry next() { + if (hit != 0) { + throw new NoSuchElementException(); + } + + ++hit; + return entry; + } + + public void remove() { + if (hit != 1) { + throw new IllegalStateException(); + } + + ++hit; + AbstractPatriciaTrie.this.removeEntry(entry); + } + } + + /** + * An {@link Iterator} for iterating over a prefix search. + */ + private final class EntryIterator extends TrieIterator> { + + // values to reset the subtree if we remove it. + private final K prefix; + private final int offset; + private final int lengthInBits; + private boolean lastOne; + + private TrieEntry subtree; // the subtree to search within + + /** + * Starts iteration at the given entry & search only + * within the given subtree. + */ + EntryIterator(final TrieEntry startScan, final K prefix, + final int offset, final int lengthInBits) { + subtree = startScan; + next = AbstractPatriciaTrie.this.followLeft(startScan); + this.prefix = prefix; + this.offset = offset; + this.lengthInBits = lengthInBits; + } + + public Map.Entry next() { + final Map.Entry entry = nextEntry(); + if (lastOne) { + next = null; + } + return entry; + } + + @Override + protected TrieEntry findNext(final TrieEntry prior) { + return AbstractPatriciaTrie.this.nextEntryInSubtree(prior, subtree); + } + + @Override + public void remove() { + // If the current entry we're removing is the subtree + // then we need to find a new subtree parent. + boolean needsFixing = false; + final int bitIdx = subtree.bitIndex; + if (current == subtree) { + needsFixing = true; + } + + super.remove(); + + // If the subtree changed its bitIndex or we + // removed the old subtree, get a new one. + if (bitIdx != subtree.bitIndex || needsFixing) { + subtree = subtree(prefix, offset, lengthInBits); + } + + // If the subtree's bitIndex is less than the + // length of our prefix, it's the last item + // in the prefix tree. + if (lengthInBits >= subtree.bitIndex) { + lastOne = true; + } + } + } + } + + //----------------------------------------------------------------------- + + /** + * Reads the content of the stream. + */ + @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect + private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException{ + stream.defaultReadObject(); + root = new TrieEntry(null, null, -1); + int size = stream.readInt(); + for(int i = 0; i < size; i++){ + K k = (K) stream.readObject(); + V v = (V) stream.readObject(); + put(k, v); + } + } + + /** + * Writes the content to the stream for serialization. + */ + private void writeObject(final ObjectOutputStream stream) throws IOException{ + stream.defaultWriteObject(); + stream.writeInt(this.size()); + for (final Entry entry : entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/trie/KeyAnalyzer.java b/fine-commons-collections4/src/org/apache/commons/collections4/trie/KeyAnalyzer.java new file mode 100644 index 000000000..b009bc1fc --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/trie/KeyAnalyzer.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.trie; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * Defines the interface to analyze {@link org.apache.commons.collections4.Trie Trie} keys on a bit level. + * {@link KeyAnalyzer}'s methods return the length of the key in bits, whether or not a bit is set, + * and bits per element in the key. + *

              + * Additionally, a method determines if a key is a prefix of another + * key and returns the bit index where one key is different from another + * key (if the key and found key are equal than the return value is + * {@link #EQUAL_BIT_KEY}). + * + * @since 4.0 + * @version $Id: KeyAnalyzer.java 1491621 2013-06-10 22:05:38Z tn $ + */ +public abstract class KeyAnalyzer implements Comparator, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = -20497563720380683L; + + /** + * Returned by {@link #bitIndex(Object, int, int, Object, int, int)} + * if key's bits are all 0. + */ + public static final int NULL_BIT_KEY = -1; + + /** + * Returned by {@link #bitIndex(Object, int, int, Object, int, int)} if key and found key are equal. + * This is a very very specific case and shouldn't happen on a regular basis. + */ + public static final int EQUAL_BIT_KEY = -2; + + public static final int OUT_OF_BOUNDS_BIT_KEY = -3; + + /** + * Returns true if bitIndex is a {@link KeyAnalyzer#OUT_OF_BOUNDS_BIT_KEY}. + */ + static boolean isOutOfBoundsIndex(final int bitIndex) { + return bitIndex == OUT_OF_BOUNDS_BIT_KEY; + } + + /** + * Returns true if bitIndex is a {@link KeyAnalyzer#EQUAL_BIT_KEY}. + */ + static boolean isEqualBitKey(final int bitIndex) { + return bitIndex == EQUAL_BIT_KEY; + } + + /** + * Returns true if bitIndex is a {@link KeyAnalyzer#NULL_BIT_KEY}. + */ + static boolean isNullBitKey(final int bitIndex) { + return bitIndex == NULL_BIT_KEY; + } + + /** + * Returns true if the given bitIndex is valid. + * Indices are considered valid if they're between 0 and {@link Integer#MAX_VALUE} + */ + static boolean isValidBitIndex(final int bitIndex) { + return bitIndex >= 0; + } + + /** + * Returns the number of bits per element in the key. + * This is only useful for variable-length keys, such as Strings. + * + * @return the number of bits per element + */ + public abstract int bitsPerElement(); + + /** + * Returns the length of the Key in bits. + * + * @param key the key + * @return the bit length of the key + */ + public abstract int lengthInBits(K key); + + /** + * Returns whether or not a bit is set. + * + * @param key the key to check, may not be null + * @param bitIndex the bit index to check + * @param lengthInBits the maximum key length in bits to check + * @return {@code true} if the bit is set in the given key and + * {@code bitIndex} < {@code lengthInBits}, {@code false} otherwise. + */ + public abstract boolean isBitSet(K key, int bitIndex, int lengthInBits); + + /** + * Returns the n-th different bit between key and other. This starts the comparison in + * key at 'offsetInBits' and goes for 'lengthInBits' bits, and compares to the other key starting + * at 'otherOffsetInBits' and going for 'otherLengthInBits' bits. + * + * @param key the key to use + * @param offsetInBits the bit offset in the key + * @param lengthInBits the maximum key length in bits to use + * @param other the other key to use + * @param otherOffsetInBits the bit offset in the other key + * @param otherLengthInBits the maximum key length in bits for the other key + * @return the bit index where the key and other first differ + */ + public abstract int bitIndex(K key, int offsetInBits, int lengthInBits, + K other, int otherOffsetInBits, int otherLengthInBits); + + /** + * Determines whether or not the given prefix (from offset to length) is a prefix of the given key. + * + * @param prefix the prefix to check + * @param offsetInBits the bit offset in the key + * @param lengthInBits the maximum key length in bits to use + * @param key the key to check + * @return {@code true} if this is a valid prefix for the given key + */ + public abstract boolean isPrefix(K prefix, int offsetInBits, int lengthInBits, K key); + + @SuppressWarnings("unchecked") + public int compare(final K o1, final K o2) { + if (o1 == null) { + return o2 == null ? 0 : -1; + } else if (o2 == null) { + return 1; + } + + return ((Comparable) o1).compareTo(o2); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/trie/PatriciaTrie.java b/fine-commons-collections4/src/org/apache/commons/collections4/trie/PatriciaTrie.java new file mode 100644 index 000000000..530fd81ad --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/trie/PatriciaTrie.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.trie; + +import java.util.Map; + +import org.apache.commons.collections4.trie.analyzer.StringKeyAnalyzer; + +/** + * Implementation of a PATRICIA Trie (Practical Algorithm to Retrieve Information + * Coded in Alphanumeric). + *

              + * A PATRICIA {@link Trie} is a compressed {@link Trie}. Instead of storing + * all data at the edges of the {@link Trie} (and having empty internal nodes), + * PATRICIA stores data in every node. This allows for very efficient traversal, + * insert, delete, predecessor, successor, prefix, range, and {@link #select(Object)} + * operations. All operations are performed at worst in O(K) time, where K + * is the number of bits in the largest item in the tree. In practice, + * operations actually take O(A(K)) time, where A(K) is the average number of + * bits of all items in the tree. + *

              + * Most importantly, PATRICIA requires very few comparisons to keys while + * doing any operation. While performing a lookup, each comparison (at most + * K of them, described above) will perform a single bit comparison against + * the given key, instead of comparing the entire key to another key. + *

              + * The {@link Trie} can return operations in lexicographical order using the + * 'prefixMap', 'submap', or 'iterator' methods. The {@link Trie} can also + * scan for items that are 'bitwise' (using an XOR metric) by the 'select' method. + * Bitwise closeness is determined by the {@link KeyAnalyzer} returning true or + * false for a bit being set or not in a given key. + *

              + * This PATRICIA {@link Trie} supports both variable length & fixed length + * keys. Some methods, such as {@link #prefixMap(Object)} are suited only + * to variable length keys. + * + * @see Radix Tree + * @see PATRICIA + * @see Crit-Bit Tree + * @since 4.0 + * @version $Id: PatriciaTrie.java 1543928 2013-11-20 20:15:35Z tn $ + */ +public class PatriciaTrie extends AbstractPatriciaTrie { + + private static final long serialVersionUID = 4446367780901817838L; + + public PatriciaTrie() { + super(new StringKeyAnalyzer()); + } + + public PatriciaTrie(final Map m) { + super(new StringKeyAnalyzer(), m); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/trie/UnmodifiableTrie.java b/fine-commons-collections4/src/org/apache/commons/collections4/trie/UnmodifiableTrie.java new file mode 100644 index 000000000..91a54fd5a --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/trie/UnmodifiableTrie.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.trie; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; + +import org.apache.commons.collections4.OrderedMapIterator; +import org.apache.commons.collections4.Trie; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableOrderedMapIterator; + +/** + * An unmodifiable {@link Trie}. + * + * @since 4.0 + * @version $Id: UnmodifiableTrie.java 1686855 2015-06-22 13:00:27Z tn $ + */ +public class UnmodifiableTrie implements Trie, Serializable, Unmodifiable { + + /** Serialization version */ + private static final long serialVersionUID = -7156426030315945159L; + + private final Trie delegate; + + /** + * Factory method to create a unmodifiable trie. + * + * @param the key type + * @param the value type + * @param trie the trie to decorate, must not be null + * @return a new unmodifiable trie + * @throws NullPointerException if trie is null + */ + public static Trie unmodifiableTrie(final Trie trie) { + if (trie instanceof Unmodifiable) { + @SuppressWarnings("unchecked") // safe to upcast + final Trie tmpTrie = (Trie) trie; + return tmpTrie; + } + return new UnmodifiableTrie(trie); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * + * @param trie the trie to decorate, must not be null + * @throws NullPointerException if trie is null + */ + public UnmodifiableTrie(final Trie trie) { + if (trie == null) { + throw new NullPointerException("Trie must not be null"); + } + @SuppressWarnings("unchecked") // safe to upcast + final Trie tmpTrie = (Trie) trie; + this.delegate = tmpTrie; + } + + //----------------------------------------------------------------------- + + public Set> entrySet() { + return Collections.unmodifiableSet(delegate.entrySet()); + } + + public Set keySet() { + return Collections.unmodifiableSet(delegate.keySet()); + } + + public Collection values() { + return Collections.unmodifiableCollection(delegate.values()); + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public boolean containsKey(final Object key) { + return delegate.containsKey(key); + } + + public boolean containsValue(final Object value) { + return delegate.containsValue(value); + } + + public V get(final Object key) { + return delegate.get(key); + } + + public boolean isEmpty() { + return delegate.isEmpty(); + } + + public V put(final K key, final V value) { + throw new UnsupportedOperationException(); + } + + public void putAll(final Map m) { + throw new UnsupportedOperationException(); + } + + public V remove(final Object key) { + throw new UnsupportedOperationException(); + } + + public int size() { + return delegate.size(); + } + + public K firstKey() { + return delegate.firstKey(); + } + + public SortedMap headMap(final K toKey) { + return Collections.unmodifiableSortedMap(delegate.headMap(toKey)); + } + + public K lastKey() { + return delegate.lastKey(); + } + + public SortedMap subMap(final K fromKey, final K toKey) { + return Collections.unmodifiableSortedMap(delegate.subMap(fromKey, toKey)); + } + + public SortedMap tailMap(final K fromKey) { + return Collections.unmodifiableSortedMap(delegate.tailMap(fromKey)); + } + + public SortedMap prefixMap(final K key) { + return Collections.unmodifiableSortedMap(delegate.prefixMap(key)); + } + + public Comparator comparator() { + return delegate.comparator(); + } + + //----------------------------------------------------------------------- + public OrderedMapIterator mapIterator() { + final OrderedMapIterator it = delegate.mapIterator(); + return UnmodifiableOrderedMapIterator.unmodifiableOrderedMapIterator(it); + } + + public K nextKey(K key) { + return delegate.nextKey(key); + } + + public K previousKey(K key) { + return delegate.previousKey(key); + } + + //----------------------------------------------------------------------- + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + return delegate.equals(obj); + } + + @Override + public String toString() { + return delegate.toString(); + } + +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/trie/analyzer/StringKeyAnalyzer.java b/fine-commons-collections4/src/org/apache/commons/collections4/trie/analyzer/StringKeyAnalyzer.java new file mode 100644 index 000000000..beeb515b8 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/trie/analyzer/StringKeyAnalyzer.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.trie.analyzer; + +import org.apache.commons.collections4.trie.KeyAnalyzer; + +/** + * An {@link KeyAnalyzer} for {@link String}s. + * + * @since 4.0 + * @version $Id: StringKeyAnalyzer.java 1543168 2013-11-18 21:22:43Z ggregory $ + */ +public class StringKeyAnalyzer extends KeyAnalyzer { + + private static final long serialVersionUID = -7032449491269434877L; + + /** A singleton instance of {@link StringKeyAnalyzer}. */ + public static final StringKeyAnalyzer INSTANCE = new StringKeyAnalyzer(); + + /** The number of bits per {@link Character}. */ + public static final int LENGTH = Character.SIZE; + + /** A bit mask where the first bit is 1 and the others are zero. */ + private static final int MSB = 0x8000; + + /** Returns a bit mask where the given bit is set. */ + private static int mask(final int bit) { + return MSB >>> bit; + } + + @Override + public int bitsPerElement() { + return LENGTH; + } + + @Override + public int lengthInBits(final String key) { + return key != null ? key.length() * LENGTH : 0; + } + + @Override + public int bitIndex(final String key, final int offsetInBits, final int lengthInBits, + final String other, final int otherOffsetInBits, final int otherLengthInBits) { + + boolean allNull = true; + + if (offsetInBits % LENGTH != 0 || otherOffsetInBits % LENGTH != 0 + || lengthInBits % LENGTH != 0 || otherLengthInBits % LENGTH != 0) { + throw new IllegalArgumentException("The offsets and lengths must be at Character boundaries"); + } + + final int beginIndex1 = offsetInBits / LENGTH; + final int beginIndex2 = otherOffsetInBits / LENGTH; + + final int endIndex1 = beginIndex1 + lengthInBits / LENGTH; + final int endIndex2 = beginIndex2 + otherLengthInBits / LENGTH; + + final int length = Math.max(endIndex1, endIndex2); + + // Look at each character, and if they're different + // then figure out which bit makes the difference + // and return it. + char k = 0, f = 0; + for(int i = 0; i < length; i++) { + final int index1 = beginIndex1 + i; + final int index2 = beginIndex2 + i; + + if (index1 >= endIndex1) { + k = 0; + } else { + k = key.charAt(index1); + } + + if (other == null || index2 >= endIndex2) { + f = 0; + } else { + f = other.charAt(index2); + } + + if (k != f) { + final int x = k ^ f; + return i * LENGTH + Integer.numberOfLeadingZeros(x) - LENGTH; + } + + if (k != 0) { + allNull = false; + } + } + + // All bits are 0 + if (allNull) { + return KeyAnalyzer.NULL_BIT_KEY; + } + + // Both keys are equal + return KeyAnalyzer.EQUAL_BIT_KEY; + } + + @Override + public boolean isBitSet(final String key, final int bitIndex, final int lengthInBits) { + if (key == null || bitIndex >= lengthInBits) { + return false; + } + + final int index = bitIndex / LENGTH; + final int bit = bitIndex % LENGTH; + + return (key.charAt(index) & mask(bit)) != 0; + } + + @Override + public boolean isPrefix(final String prefix, final int offsetInBits, + final int lengthInBits, final String key) { + if (offsetInBits % LENGTH != 0 || lengthInBits % LENGTH != 0) { + throw new IllegalArgumentException( + "Cannot determine prefix outside of Character boundaries"); + } + + final String s1 = prefix.substring(offsetInBits / LENGTH, lengthInBits / LENGTH); + return key.startsWith(s1); + } +} diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/trie/analyzer/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/trie/analyzer/package-info.java new file mode 100644 index 000000000..5eeb5d31e --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/trie/analyzer/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains various {@link org.apache.commons.collections4.trie.KeyAnalyzer} implementations. + * + * @version $Id: package-info.java 1491615 2013-06-10 21:46:19Z tn $ + */ +package org.apache.commons.collections4.trie.analyzer; diff --git a/fine-commons-collections4/src/org/apache/commons/collections4/trie/package-info.java b/fine-commons-collections4/src/org/apache/commons/collections4/trie/package-info.java new file mode 100644 index 000000000..cd0a29854 --- /dev/null +++ b/fine-commons-collections4/src/org/apache/commons/collections4/trie/package-info.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This package contains implementations of the + * {@link org.apache.commons.collections4.Trie Trie} interface. + *

              + * The implementations are in the form of direct implementations and decorators. + * A decorator wraps another implementation of the interface to add some + * specific additional functionality. + *

              + * The following implementations are provided in the package: + *

                + *
              • PatriciaTrie - an implementation of a PATRICIA trie + *
              + *

              + * The following decorators are provided: + *

                + *
              • Unmodifiable - ensures the collection cannot be altered + *
              + * + * @version $Id: package-info.java 1493523 2013-06-16 15:56:35Z tn $ + */ +package org.apache.commons.collections4.trie; diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/AnnotationUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/AnnotationUtils.java new file mode 100644 index 000000000..cbb6c79a7 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/AnnotationUtils.java @@ -0,0 +1,371 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; + +import com.fr.third.org.apache.commons.lang3.builder.ToStringBuilder; +import com.fr.third.org.apache.commons.lang3.builder.ToStringStyle; + +/** + *

              Helper methods for working with {@link Annotation} instances.

              + * + *

              This class contains various utility methods that make working with + * annotations simpler.

              + * + *

              {@link Annotation} instances are always proxy objects; unfortunately + * dynamic proxies cannot be depended upon to know how to implement certain + * methods in the same manner as would be done by "natural" {@link Annotation}s. + * The methods presented in this class can be used to avoid that possibility. It + * is of course also possible for dynamic proxies to actually delegate their + * e.g. {@link Annotation#equals(Object)}/{@link Annotation#hashCode()}/ + * {@link Annotation#toString()} implementations to {@link AnnotationUtils}.

              + * + *

              #ThreadSafe#

              + * + * @since 3.0 + * @version $Id: AnnotationUtils.java 1436770 2013-01-22 07:09:45Z ggregory $ + */ +public class AnnotationUtils { + + /** + * A style that prints annotations as recommended. + */ + private static final ToStringStyle TO_STRING_STYLE = new ToStringStyle() { + /** Serialization version */ + private static final long serialVersionUID = 1L; + + { + setDefaultFullDetail(true); + setArrayContentDetail(true); + setUseClassName(true); + setUseShortClassName(true); + setUseIdentityHashCode(false); + setContentStart("("); + setContentEnd(")"); + setFieldSeparator(", "); + setArrayStart("["); + setArrayEnd("]"); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getShortClassName(final java.lang.Class cls) { + Class annotationType = null; + for (final Class iface : ClassUtils.getAllInterfaces(cls)) { + if (Annotation.class.isAssignableFrom(iface)) { + @SuppressWarnings("unchecked") // OK because we just checked the assignability + final + Class found = (Class) iface; + annotationType = found; + break; + } + } + return new StringBuilder(annotationType == null ? "" : annotationType.getName()) + .insert(0, '@').toString(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) { + if (value instanceof Annotation) { + value = AnnotationUtils.toString((Annotation) value); + } + super.appendDetail(buffer, fieldName, value); + } + + }; + + /** + *

              {@code AnnotationUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used statically.

              + * + *

              This constructor is public to permit tools that require a JavaBean + * instance to operate.

              + */ + public AnnotationUtils() { + } + + //----------------------------------------------------------------------- + /** + *

              Checks if two annotations are equal using the criteria for equality + * presented in the {@link Annotation#equals(Object)} API docs.

              + * + * @param a1 the first Annotation to compare, {@code null} returns + * {@code false} unless both are {@code null} + * @param a2 the second Annotation to compare, {@code null} returns + * {@code false} unless both are {@code null} + * @return {@code true} if the two annotations are {@code equal} or both + * {@code null} + */ + public static boolean equals(final Annotation a1, final Annotation a2) { + if (a1 == a2) { + return true; + } + if (a1 == null || a2 == null) { + return false; + } + final Class type = a1.annotationType(); + final Class type2 = a2.annotationType(); + Validate.notNull(type, "Annotation %s with null annotationType()", a1); + Validate.notNull(type2, "Annotation %s with null annotationType()", a2); + if (!type.equals(type2)) { + return false; + } + try { + for (final Method m : type.getDeclaredMethods()) { + if (m.getParameterTypes().length == 0 + && isValidAnnotationMemberType(m.getReturnType())) { + final Object v1 = m.invoke(a1); + final Object v2 = m.invoke(a2); + if (!memberEquals(m.getReturnType(), v1, v2)) { + return false; + } + } + } + } catch (final IllegalAccessException ex) { + return false; + } catch (final InvocationTargetException ex) { + return false; + } + return true; + } + + /** + *

              Generate a hash code for the given annotation using the algorithm + * presented in the {@link Annotation#hashCode()} API docs.

              + * + * @param a the Annotation for a hash code calculation is desired, not + * {@code null} + * @return the calculated hash code + * @throws RuntimeException if an {@code Exception} is encountered during + * annotation member access + * @throws IllegalStateException if an annotation method invocation returns + * {@code null} + */ + public static int hashCode(final Annotation a) { + int result = 0; + final Class type = a.annotationType(); + for (final Method m : type.getDeclaredMethods()) { + try { + final Object value = m.invoke(a); + if (value == null) { + throw new IllegalStateException( + String.format("Annotation method %s returned null", m)); + } + result += hashMember(m.getName(), value); + } catch (final RuntimeException ex) { + throw ex; + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + return result; + } + + /** + *

              Generate a string representation of an Annotation, as suggested by + * {@link Annotation#toString()}.

              + * + * @param a the annotation of which a string representation is desired + * @return the standard string representation of an annotation, not + * {@code null} + */ + public static String toString(final Annotation a) { + final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE); + for (final Method m : a.annotationType().getDeclaredMethods()) { + if (m.getParameterTypes().length > 0) { + continue; //wtf? + } + try { + builder.append(m.getName(), m.invoke(a)); + } catch (final RuntimeException ex) { + throw ex; + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + return builder.build(); + } + + /** + *

              Checks if the specified type is permitted as an annotation member.

              + * + *

              The Java language specification only permits certain types to be used + * in annotations. These include {@link String}, {@link Class}, primitive + * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of + * these types.

              + * + * @param type the type to check, {@code null} + * @return {@code true} if the type is a valid type to use in an annotation + */ + public static boolean isValidAnnotationMemberType(Class type) { + if (type == null) { + return false; + } + if (type.isArray()) { + type = type.getComponentType(); + } + return type.isPrimitive() || type.isEnum() || type.isAnnotation() + || String.class.equals(type) || Class.class.equals(type); + } + + //besides modularity, this has the advantage of autoboxing primitives: + /** + * Helper method for generating a hash code for a member of an annotation. + * + * @param name the name of the member + * @param value the value of the member + * @return a hash code for this member + */ + private static int hashMember(final String name, final Object value) { + final int part1 = name.hashCode() * 127; + if (value.getClass().isArray()) { + return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value); + } + if (value instanceof Annotation) { + return part1 ^ hashCode((Annotation) value); + } + return part1 ^ value.hashCode(); + } + + /** + * Helper method for checking whether two objects of the given type are + * equal. This method is used to compare the parameters of two annotation + * instances. + * + * @param type the type of the objects to be compared + * @param o1 the first object + * @param o2 the second object + * @return a flag whether these objects are equal + */ + private static boolean memberEquals(final Class type, final Object o1, final Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + if (type.isArray()) { + return arrayMemberEquals(type.getComponentType(), o1, o2); + } + if (type.isAnnotation()) { + return equals((Annotation) o1, (Annotation) o2); + } + return o1.equals(o2); + } + + /** + * Helper method for comparing two objects of an array type. + * + * @param componentType the component type of the array + * @param o1 the first object + * @param o2 the second object + * @return a flag whether these objects are equal + */ + private static boolean arrayMemberEquals(final Class componentType, final Object o1, final Object o2) { + if (componentType.isAnnotation()) { + return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2); + } + if (componentType.equals(Byte.TYPE)) { + return Arrays.equals((byte[]) o1, (byte[]) o2); + } + if (componentType.equals(Short.TYPE)) { + return Arrays.equals((short[]) o1, (short[]) o2); + } + if (componentType.equals(Integer.TYPE)) { + return Arrays.equals((int[]) o1, (int[]) o2); + } + if (componentType.equals(Character.TYPE)) { + return Arrays.equals((char[]) o1, (char[]) o2); + } + if (componentType.equals(Long.TYPE)) { + return Arrays.equals((long[]) o1, (long[]) o2); + } + if (componentType.equals(Float.TYPE)) { + return Arrays.equals((float[]) o1, (float[]) o2); + } + if (componentType.equals(Double.TYPE)) { + return Arrays.equals((double[]) o1, (double[]) o2); + } + if (componentType.equals(Boolean.TYPE)) { + return Arrays.equals((boolean[]) o1, (boolean[]) o2); + } + return Arrays.equals((Object[]) o1, (Object[]) o2); + } + + /** + * Helper method for comparing two arrays of annotations. + * + * @param a1 the first array + * @param a2 the second array + * @return a flag whether these arrays are equal + */ + private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) { + if (a1.length != a2.length) { + return false; + } + for (int i = 0; i < a1.length; i++) { + if (!equals(a1[i], a2[i])) { + return false; + } + } + return true; + } + + /** + * Helper method for generating a hash code for an array. + * + * @param componentType the component type of the array + * @param o the array + * @return a hash code for the specified array + */ + private static int arrayMemberHash(final Class componentType, final Object o) { + if (componentType.equals(Byte.TYPE)) { + return Arrays.hashCode((byte[]) o); + } + if (componentType.equals(Short.TYPE)) { + return Arrays.hashCode((short[]) o); + } + if (componentType.equals(Integer.TYPE)) { + return Arrays.hashCode((int[]) o); + } + if (componentType.equals(Character.TYPE)) { + return Arrays.hashCode((char[]) o); + } + if (componentType.equals(Long.TYPE)) { + return Arrays.hashCode((long[]) o); + } + if (componentType.equals(Float.TYPE)) { + return Arrays.hashCode((float[]) o); + } + if (componentType.equals(Double.TYPE)) { + return Arrays.hashCode((double[]) o); + } + if (componentType.equals(Boolean.TYPE)) { + return Arrays.hashCode((boolean[]) o); + } + return Arrays.hashCode((Object[]) o); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ArrayUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ArrayUtils.java new file mode 100644 index 000000000..bfe573fb7 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ArrayUtils.java @@ -0,0 +1,6343 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +import com.fr.third.org.apache.commons.lang3.builder.EqualsBuilder; +import com.fr.third.org.apache.commons.lang3.builder.HashCodeBuilder; +import com.fr.third.org.apache.commons.lang3.builder.ToStringBuilder; +import com.fr.third.org.apache.commons.lang3.builder.ToStringStyle; +import com.fr.third.org.apache.commons.lang3.math.NumberUtils; +import com.fr.third.org.apache.commons.lang3.mutable.MutableInt; + +/** + *

              Operations on arrays, primitive arrays (like {@code int[]}) and + * primitive wrapper arrays (like {@code Integer[]}).

              + * + *

              This class tries to handle {@code null} input gracefully. + * An exception will not be thrown for a {@code null} + * array input. However, an Object array that contains a {@code null} + * element may throw an exception. Each method documents its behaviour.

              + * + *

              #ThreadSafe#

              + * @since 2.0 + * @version $Id: ArrayUtils.java 1645483 2014-12-14 18:22:06Z kinow $ + */ +public class ArrayUtils { + + /** + * An empty immutable {@code Object} array. + */ + public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + /** + * An empty immutable {@code Class} array. + */ + public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + /** + * An empty immutable {@code String} array. + */ + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + /** + * An empty immutable {@code long} array. + */ + public static final long[] EMPTY_LONG_ARRAY = new long[0]; + /** + * An empty immutable {@code Long} array. + */ + public static final Long[] EMPTY_LONG_OBJECT_ARRAY = new Long[0]; + /** + * An empty immutable {@code int} array. + */ + public static final int[] EMPTY_INT_ARRAY = new int[0]; + /** + * An empty immutable {@code Integer} array. + */ + public static final Integer[] EMPTY_INTEGER_OBJECT_ARRAY = new Integer[0]; + /** + * An empty immutable {@code short} array. + */ + public static final short[] EMPTY_SHORT_ARRAY = new short[0]; + /** + * An empty immutable {@code Short} array. + */ + public static final Short[] EMPTY_SHORT_OBJECT_ARRAY = new Short[0]; + /** + * An empty immutable {@code byte} array. + */ + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + /** + * An empty immutable {@code Byte} array. + */ + public static final Byte[] EMPTY_BYTE_OBJECT_ARRAY = new Byte[0]; + /** + * An empty immutable {@code double} array. + */ + public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; + /** + * An empty immutable {@code Double} array. + */ + public static final Double[] EMPTY_DOUBLE_OBJECT_ARRAY = new Double[0]; + /** + * An empty immutable {@code float} array. + */ + public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; + /** + * An empty immutable {@code Float} array. + */ + public static final Float[] EMPTY_FLOAT_OBJECT_ARRAY = new Float[0]; + /** + * An empty immutable {@code boolean} array. + */ + public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; + /** + * An empty immutable {@code Boolean} array. + */ + public static final Boolean[] EMPTY_BOOLEAN_OBJECT_ARRAY = new Boolean[0]; + /** + * An empty immutable {@code char} array. + */ + public static final char[] EMPTY_CHAR_ARRAY = new char[0]; + /** + * An empty immutable {@code Character} array. + */ + public static final Character[] EMPTY_CHARACTER_OBJECT_ARRAY = new Character[0]; + + /** + * The index value when an element is not found in a list or array: {@code -1}. + * This value is returned by methods in this class and can also be used in comparisons with values returned by + * various method from {@link java.util.List}. + */ + public static final int INDEX_NOT_FOUND = -1; + + /** + *

              ArrayUtils instances should NOT be constructed in standard programming. + * Instead, the class should be used as ArrayUtils.clone(new int[] {2}).

              + * + *

              This constructor is public to permit tools that require a JavaBean instance + * to operate.

              + */ + public ArrayUtils() { + super(); + } + + + // NOTE: Cannot use {@code} to enclose text which includes {}, but is OK + + + // Basic methods handling multi-dimensional arrays + //----------------------------------------------------------------------- + /** + *

              Outputs an array as a String, treating {@code null} as an empty array.

              + * + *

              Multi-dimensional arrays are handled correctly, including + * multi-dimensional primitive arrays.

              + * + *

              The format is that of Java source code, for example {a,b}.

              + * + * @param array the array to get a toString for, may be {@code null} + * @return a String representation of the array, '{}' if null array input + */ + public static String toString(final Object array) { + return toString(array, "{}"); + } + + /** + *

              Outputs an array as a String handling {@code null}s.

              + * + *

              Multi-dimensional arrays are handled correctly, including + * multi-dimensional primitive arrays.

              + * + *

              The format is that of Java source code, for example {a,b}.

              + * + * @param array the array to get a toString for, may be {@code null} + * @param stringIfNull the String to return if the array is {@code null} + * @return a String representation of the array + */ + public static String toString(final Object array, final String stringIfNull) { + if (array == null) { + return stringIfNull; + } + return new ToStringBuilder(array, ToStringStyle.SIMPLE_STYLE).append(array).toString(); + } + + /** + *

              Get a hash code for an array handling multi-dimensional arrays correctly.

              + * + *

              Multi-dimensional primitive arrays are also handled correctly by this method.

              + * + * @param array the array to get a hash code for, {@code null} returns zero + * @return a hash code for the array + */ + public static int hashCode(final Object array) { + return new HashCodeBuilder().append(array).toHashCode(); + } + + /** + *

              Compares two arrays, using equals(), handling multi-dimensional arrays + * correctly.

              + * + *

              Multi-dimensional primitive arrays are also handled correctly by this method.

              + * + * @param array1 the left hand array to compare, may be {@code null} + * @param array2 the right hand array to compare, may be {@code null} + * @return {@code true} if the arrays are equal + * @deprecated this method has been replaced by {@code java.util.Objects.deepEquals(Object, Object)} and will be + * removed from future releases. + */ + @Deprecated + public static boolean isEquals(final Object array1, final Object array2) { + return new EqualsBuilder().append(array1, array2).isEquals(); + } + + // To map + //----------------------------------------------------------------------- + /** + *

              Converts the given array into a {@link java.util.Map}. Each element of the array + * must be either a {@link java.util.Map.Entry} or an Array, containing at least two + * elements, where the first element is used as key and the second as + * value.

              + * + *

              This method can be used to initialize:

              + *
              +     * // Create a Map mapping colors.
              +     * Map colorMap = MapUtils.toMap(new String[][] {{
              +     *     {"RED", "#FF0000"},
              +     *     {"GREEN", "#00FF00"},
              +     *     {"BLUE", "#0000FF"}});
              +     * 
              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array an array whose elements are either a {@link java.util.Map.Entry} or + * an Array containing at least two elements, may be {@code null} + * @return a {@code Map} that was created from the array + * @throws IllegalArgumentException if one element of this Array is + * itself an Array containing less then two elements + * @throws IllegalArgumentException if the array contains elements other + * than {@link java.util.Map.Entry} and an Array + */ + public static Map toMap(final Object[] array) { + if (array == null) { + return null; + } + final Map map = new HashMap((int) (array.length * 1.5)); + for (int i = 0; i < array.length; i++) { + final Object object = array[i]; + if (object instanceof Map.Entry) { + final Map.Entry entry = (Map.Entry) object; + map.put(entry.getKey(), entry.getValue()); + } else if (object instanceof Object[]) { + final Object[] entry = (Object[]) object; + if (entry.length < 2) { + throw new IllegalArgumentException("Array element " + i + ", '" + + object + + "', has a length less than 2"); + } + map.put(entry[0], entry[1]); + } else { + throw new IllegalArgumentException("Array element " + i + ", '" + + object + + "', is neither of type Map.Entry nor an Array"); + } + } + return map; + } + + // Generic array + //----------------------------------------------------------------------- + /** + *

              Create a type-safe generic array.

              + * + *

              The Java language does not allow an array to be created from a generic type:

              + * + *
              +    public static <T> T[] createAnArray(int size) {
              +        return new T[size]; // compiler error here
              +    }
              +    public static <T> T[] createAnArray(int size) {
              +        return (T[])new Object[size]; // ClassCastException at runtime
              +    }
              +     * 
              + * + *

              Therefore new arrays of generic types can be created with this method. + * For example, an array of Strings can be created:

              + * + *
              +    String[] array = ArrayUtils.toArray("1", "2");
              +    String[] emptyArray = ArrayUtils.<String>toArray();
              +     * 
              + * + *

              The method is typically used in scenarios, where the caller itself uses generic types + * that have to be combined into an array.

              + * + *

              Note, this method makes only sense to provide arguments of the same type so that the + * compiler can deduce the type of the array itself. While it is possible to select the + * type explicitly like in + * Number[] array = ArrayUtils.<Number>toArray(Integer.valueOf(42), Double.valueOf(Math.PI)), + * there is no real advantage when compared to + * new Number[] {Integer.valueOf(42), Double.valueOf(Math.PI)}.

              + * + * @param the array's element type + * @param items the varargs array items, null allowed + * @return the array, not null unless a null array is passed in + * @since 3.0 + */ + public static T[] toArray(final T... items) { + return items; + } + + // Clone + //----------------------------------------------------------------------- + /** + *

              Shallow clones an array returning a typecast result and handling + * {@code null}.

              + * + *

              The objects in the array are not cloned, thus there is no special + * handling for multi-dimensional arrays.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param the component type of the array + * @param array the array to shallow clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static T[] clone(final T[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

              Clones an array returning a typecast result and handling + * {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static long[] clone(final long[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

              Clones an array returning a typecast result and handling + * {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static int[] clone(final int[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

              Clones an array returning a typecast result and handling + * {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static short[] clone(final short[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

              Clones an array returning a typecast result and handling + * {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static char[] clone(final char[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

              Clones an array returning a typecast result and handling + * {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static byte[] clone(final byte[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

              Clones an array returning a typecast result and handling + * {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static double[] clone(final double[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

              Clones an array returning a typecast result and handling + * {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static float[] clone(final float[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

              Clones an array returning a typecast result and handling + * {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array the array to clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + public static boolean[] clone(final boolean[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + // nullToEmpty + //----------------------------------------------------------------------- + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Object[] nullToEmpty(final Object[] array) { + if (isEmpty(array)) { + return EMPTY_OBJECT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 3.2 + */ + public static Class[] nullToEmpty(final Class[] array) { + if (isEmpty(array)) { + return EMPTY_CLASS_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static String[] nullToEmpty(final String[] array) { + if (isEmpty(array)) { + return EMPTY_STRING_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static long[] nullToEmpty(final long[] array) { + if (isEmpty(array)) { + return EMPTY_LONG_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static int[] nullToEmpty(final int[] array) { + if (isEmpty(array)) { + return EMPTY_INT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static short[] nullToEmpty(final short[] array) { + if (isEmpty(array)) { + return EMPTY_SHORT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static char[] nullToEmpty(final char[] array) { + if (isEmpty(array)) { + return EMPTY_CHAR_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static byte[] nullToEmpty(final byte[] array) { + if (isEmpty(array)) { + return EMPTY_BYTE_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static double[] nullToEmpty(final double[] array) { + if (isEmpty(array)) { + return EMPTY_DOUBLE_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static float[] nullToEmpty(final float[] array) { + if (isEmpty(array)) { + return EMPTY_FLOAT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static boolean[] nullToEmpty(final boolean[] array) { + if (isEmpty(array)) { + return EMPTY_BOOLEAN_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Long[] nullToEmpty(final Long[] array) { + if (isEmpty(array)) { + return EMPTY_LONG_OBJECT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Integer[] nullToEmpty(final Integer[] array) { + if (isEmpty(array)) { + return EMPTY_INTEGER_OBJECT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Short[] nullToEmpty(final Short[] array) { + if (isEmpty(array)) { + return EMPTY_SHORT_OBJECT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Character[] nullToEmpty(final Character[] array) { + if (isEmpty(array)) { + return EMPTY_CHARACTER_OBJECT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Byte[] nullToEmpty(final Byte[] array) { + if (isEmpty(array)) { + return EMPTY_BYTE_OBJECT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Double[] nullToEmpty(final Double[] array) { + if (isEmpty(array)) { + return EMPTY_DOUBLE_OBJECT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Float[] nullToEmpty(final Float[] array) { + if (isEmpty(array)) { + return EMPTY_FLOAT_OBJECT_ARRAY; + } + return array; + } + + /** + *

              Defensive programming technique to change a {@code null} + * reference to an empty one.

              + * + *

              This method returns an empty array for a {@code null} input array.

              + * + *

              As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

              + * + * @param array the array to check for {@code null} or empty + * @return the same array, {@code public static} empty array if {@code null} or empty input + * @since 2.5 + */ + public static Boolean[] nullToEmpty(final Boolean[] array) { + if (isEmpty(array)) { + return EMPTY_BOOLEAN_OBJECT_ARRAY; + } + return array; + } + + // Subarrays + //----------------------------------------------------------------------- + /** + *

              Produces a new array containing the elements between + * the start and end indices.

              + * + *

              The start index is inclusive, the end index exclusive. + * Null array input produces null output.

              + * + *

              The component type of the subarray is always the same as + * that of the input array. Thus, if the input is an array of type + * {@code Date}, the following usage is envisaged:

              + * + *
              +     * Date[] someDates = (Date[])ArrayUtils.subarray(allDates, 2, 5);
              +     * 
              + * + * @param the component type of the array + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @see Arrays#copyOfRange(Object[], int, int) + */ + public static T[] subarray(final T[] array, int startIndexInclusive, int endIndexExclusive) { + if (array == null) { + return null; + } + if (startIndexInclusive < 0) { + startIndexInclusive = 0; + } + if (endIndexExclusive > array.length) { + endIndexExclusive = array.length; + } + final int newSize = endIndexExclusive - startIndexInclusive; + final Class type = array.getClass().getComponentType(); + if (newSize <= 0) { + @SuppressWarnings("unchecked") // OK, because array is of type T + final T[] emptyArray = (T[]) Array.newInstance(type, 0); + return emptyArray; + } + @SuppressWarnings("unchecked") // OK, because array is of type T + final + T[] subarray = (T[]) Array.newInstance(type, newSize); + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + + /** + *

              Produces a new {@code long} array containing the elements + * between the start and end indices.

              + * + *

              The start index is inclusive, the end index exclusive. + * Null array input produces null output.

              + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @see Arrays#copyOfRange(long[], int, int) + */ + public static long[] subarray(final long[] array, int startIndexInclusive, int endIndexExclusive) { + if (array == null) { + return null; + } + if (startIndexInclusive < 0) { + startIndexInclusive = 0; + } + if (endIndexExclusive > array.length) { + endIndexExclusive = array.length; + } + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_LONG_ARRAY; + } + + final long[] subarray = new long[newSize]; + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + + /** + *

              Produces a new {@code int} array containing the elements + * between the start and end indices.

              + * + *

              The start index is inclusive, the end index exclusive. + * Null array input produces null output.

              + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @see Arrays#copyOfRange(int[], int, int) + */ + public static int[] subarray(final int[] array, int startIndexInclusive, int endIndexExclusive) { + if (array == null) { + return null; + } + if (startIndexInclusive < 0) { + startIndexInclusive = 0; + } + if (endIndexExclusive > array.length) { + endIndexExclusive = array.length; + } + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_INT_ARRAY; + } + + final int[] subarray = new int[newSize]; + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + + /** + *

              Produces a new {@code short} array containing the elements + * between the start and end indices.

              + * + *

              The start index is inclusive, the end index exclusive. + * Null array input produces null output.

              + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @see Arrays#copyOfRange(short[], int, int) + */ + public static short[] subarray(final short[] array, int startIndexInclusive, int endIndexExclusive) { + if (array == null) { + return null; + } + if (startIndexInclusive < 0) { + startIndexInclusive = 0; + } + if (endIndexExclusive > array.length) { + endIndexExclusive = array.length; + } + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_SHORT_ARRAY; + } + + final short[] subarray = new short[newSize]; + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + + /** + *

              Produces a new {@code char} array containing the elements + * between the start and end indices.

              + * + *

              The start index is inclusive, the end index exclusive. + * Null array input produces null output.

              + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @see Arrays#copyOfRange(char[], int, int) + */ + public static char[] subarray(final char[] array, int startIndexInclusive, int endIndexExclusive) { + if (array == null) { + return null; + } + if (startIndexInclusive < 0) { + startIndexInclusive = 0; + } + if (endIndexExclusive > array.length) { + endIndexExclusive = array.length; + } + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_CHAR_ARRAY; + } + + final char[] subarray = new char[newSize]; + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + + /** + *

              Produces a new {@code byte} array containing the elements + * between the start and end indices.

              + * + *

              The start index is inclusive, the end index exclusive. + * Null array input produces null output.

              + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @see Arrays#copyOfRange(byte[], int, int) + */ + public static byte[] subarray(final byte[] array, int startIndexInclusive, int endIndexExclusive) { + if (array == null) { + return null; + } + if (startIndexInclusive < 0) { + startIndexInclusive = 0; + } + if (endIndexExclusive > array.length) { + endIndexExclusive = array.length; + } + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_BYTE_ARRAY; + } + + final byte[] subarray = new byte[newSize]; + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + + /** + *

              Produces a new {@code double} array containing the elements + * between the start and end indices.

              + * + *

              The start index is inclusive, the end index exclusive. + * Null array input produces null output.

              + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @see Arrays#copyOfRange(double[], int, int) + */ + public static double[] subarray(final double[] array, int startIndexInclusive, int endIndexExclusive) { + if (array == null) { + return null; + } + if (startIndexInclusive < 0) { + startIndexInclusive = 0; + } + if (endIndexExclusive > array.length) { + endIndexExclusive = array.length; + } + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_DOUBLE_ARRAY; + } + + final double[] subarray = new double[newSize]; + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + + /** + *

              Produces a new {@code float} array containing the elements + * between the start and end indices.

              + * + *

              The start index is inclusive, the end index exclusive. + * Null array input produces null output.

              + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @see Arrays#copyOfRange(float[], int, int) + */ + public static float[] subarray(final float[] array, int startIndexInclusive, int endIndexExclusive) { + if (array == null) { + return null; + } + if (startIndexInclusive < 0) { + startIndexInclusive = 0; + } + if (endIndexExclusive > array.length) { + endIndexExclusive = array.length; + } + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_FLOAT_ARRAY; + } + + final float[] subarray = new float[newSize]; + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + + /** + *

              Produces a new {@code boolean} array containing the elements + * between the start and end indices.

              + * + *

              The start index is inclusive, the end index exclusive. + * Null array input produces null output.

              + * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>array.length) is demoted to + * array length. + * @return a new array containing the elements between + * the start and end indices. + * @since 2.1 + * @see Arrays#copyOfRange(boolean[], int, int) + */ + public static boolean[] subarray(final boolean[] array, int startIndexInclusive, int endIndexExclusive) { + if (array == null) { + return null; + } + if (startIndexInclusive < 0) { + startIndexInclusive = 0; + } + if (endIndexExclusive > array.length) { + endIndexExclusive = array.length; + } + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_BOOLEAN_ARRAY; + } + + final boolean[] subarray = new boolean[newSize]; + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + + // Is same length + //----------------------------------------------------------------------- + /** + *

              Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}. + * + *

              Any multi-dimensional aspects of the arrays are ignored.

              + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + */ + public static boolean isSameLength(final Object[] array1, final Object[] array2) { + if ((array1 == null && array2 != null && array2.length > 0) || + (array2 == null && array1 != null && array1.length > 0) || + (array1 != null && array2 != null && array1.length != array2.length)) { + return false; + } + return true; + } + + /** + *

              Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

              + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + */ + public static boolean isSameLength(final long[] array1, final long[] array2) { + if ((array1 == null && array2 != null && array2.length > 0) || + (array2 == null && array1 != null && array1.length > 0) || + (array1 != null && array2 != null && array1.length != array2.length)) { + return false; + } + return true; + } + + /** + *

              Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

              + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + */ + public static boolean isSameLength(final int[] array1, final int[] array2) { + if ((array1 == null && array2 != null && array2.length > 0) || + (array2 == null && array1 != null && array1.length > 0) || + (array1 != null && array2 != null && array1.length != array2.length)) { + return false; + } + return true; + } + + /** + *

              Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

              + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + */ + public static boolean isSameLength(final short[] array1, final short[] array2) { + if ((array1 == null && array2 != null && array2.length > 0) || + (array2 == null && array1 != null && array1.length > 0) || + (array1 != null && array2 != null && array1.length != array2.length)) { + return false; + } + return true; + } + + /** + *

              Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

              + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + */ + public static boolean isSameLength(final char[] array1, final char[] array2) { + if ((array1 == null && array2 != null && array2.length > 0) || + (array2 == null && array1 != null && array1.length > 0) || + (array1 != null && array2 != null && array1.length != array2.length)) { + return false; + } + return true; + } + + /** + *

              Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

              + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + */ + public static boolean isSameLength(final byte[] array1, final byte[] array2) { + if ((array1 == null && array2 != null && array2.length > 0) || + (array2 == null && array1 != null && array1.length > 0) || + (array1 != null && array2 != null && array1.length != array2.length)) { + return false; + } + return true; + } + + /** + *

              Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

              + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + */ + public static boolean isSameLength(final double[] array1, final double[] array2) { + if ((array1 == null && array2 != null && array2.length > 0) || + (array2 == null && array1 != null && array1.length > 0) || + (array1 != null && array2 != null && array1.length != array2.length)) { + return false; + } + return true; + } + + /** + *

              Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

              + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + */ + public static boolean isSameLength(final float[] array1, final float[] array2) { + if ((array1 == null && array2 != null && array2.length > 0) || + (array2 == null && array1 != null && array1.length > 0) || + (array1 != null && array2 != null && array1.length != array2.length)) { + return false; + } + return true; + } + + /** + *

              Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

              + * + * @param array1 the first array, may be {@code null} + * @param array2 the second array, may be {@code null} + * @return {@code true} if length of arrays matches, treating + * {@code null} as an empty array + */ + public static boolean isSameLength(final boolean[] array1, final boolean[] array2) { + if ((array1 == null && array2 != null && array2.length > 0) || + (array2 == null && array1 != null && array1.length > 0) || + (array1 != null && array2 != null && array1.length != array2.length)) { + return false; + } + return true; + } + + //----------------------------------------------------------------------- + /** + *

              Returns the length of the specified array. + * This method can deal with {@code Object} arrays and with primitive arrays.

              + * + *

              If the input array is {@code null}, {@code 0} is returned.

              + * + *
              +     * ArrayUtils.getLength(null)            = 0
              +     * ArrayUtils.getLength([])              = 0
              +     * ArrayUtils.getLength([null])          = 1
              +     * ArrayUtils.getLength([true, false])   = 2
              +     * ArrayUtils.getLength([1, 2, 3])       = 3
              +     * ArrayUtils.getLength(["a", "b", "c"]) = 3
              +     * 
              + * + * @param array the array to retrieve the length from, may be null + * @return The length of the array, or {@code 0} if the array is {@code null} + * @throws IllegalArgumentException if the object argument is not an array. + * @since 2.1 + */ + public static int getLength(final Object array) { + if (array == null) { + return 0; + } + return Array.getLength(array); + } + + /** + *

              Checks whether two arrays are the same type taking into account + * multi-dimensional arrays.

              + * + * @param array1 the first array, must not be {@code null} + * @param array2 the second array, must not be {@code null} + * @return {@code true} if type of arrays matches + * @throws IllegalArgumentException if either array is {@code null} + */ + public static boolean isSameType(final Object array1, final Object array2) { + if (array1 == null || array2 == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + return array1.getClass().getName().equals(array2.getClass().getName()); + } + + // Reverse + //----------------------------------------------------------------------- + /** + *

              Reverses the order of the given array.

              + * + *

              There is no special handling for multi-dimensional arrays.

              + * + *

              This method does nothing for a {@code null} input array.

              + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final Object[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

              Reverses the order of the given array.

              + * + *

              This method does nothing for a {@code null} input array.

              + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final long[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

              Reverses the order of the given array.

              + * + *

              This method does nothing for a {@code null} input array.

              + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final int[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

              Reverses the order of the given array.

              + * + *

              This method does nothing for a {@code null} input array.

              + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final short[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

              Reverses the order of the given array.

              + * + *

              This method does nothing for a {@code null} input array.

              + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final char[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

              Reverses the order of the given array.

              + * + *

              This method does nothing for a {@code null} input array.

              + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final byte[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

              Reverses the order of the given array.

              + * + *

              This method does nothing for a {@code null} input array.

              + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final double[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

              Reverses the order of the given array.

              + * + *

              This method does nothing for a {@code null} input array.

              + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final float[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

              Reverses the order of the given array.

              + * + *

              This method does nothing for a {@code null} input array.

              + * + * @param array the array to reverse, may be {@code null} + */ + public static void reverse(final boolean[] array) { + if (array == null) { + return; + } + reverse(array, 0, array.length); + } + + /** + *

              + * Reverses the order of the given array in the given range. + *

              + * + *

              + * This method does nothing for a {@code null} input array. + *

              + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @since 3.2 + */ + public static void reverse(final boolean[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = startIndexInclusive < 0 ? 0 : startIndexInclusive; + int j = Math.min(array.length, endIndexExclusive) - 1; + boolean tmp; + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + /** + *

              + * Reverses the order of the given array in the given range. + *

              + * + *

              + * This method does nothing for a {@code null} input array. + *

              + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @since 3.2 + */ + public static void reverse(final byte[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = startIndexInclusive < 0 ? 0 : startIndexInclusive; + int j = Math.min(array.length, endIndexExclusive) - 1; + byte tmp; + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + /** + *

              + * Reverses the order of the given array in the given range. + *

              + * + *

              + * This method does nothing for a {@code null} input array. + *

              + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @since 3.2 + */ + public static void reverse(final char[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = startIndexInclusive < 0 ? 0 : startIndexInclusive; + int j = Math.min(array.length, endIndexExclusive) - 1; + char tmp; + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + /** + *

              + * Reverses the order of the given array in the given range. + *

              + * + *

              + * This method does nothing for a {@code null} input array. + *

              + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @since 3.2 + */ + public static void reverse(final double[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = startIndexInclusive < 0 ? 0 : startIndexInclusive; + int j = Math.min(array.length, endIndexExclusive) - 1; + double tmp; + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + /** + *

              + * Reverses the order of the given array in the given range. + *

              + * + *

              + * This method does nothing for a {@code null} input array. + *

              + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @since 3.2 + */ + public static void reverse(final float[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = startIndexInclusive < 0 ? 0 : startIndexInclusive; + int j = Math.min(array.length, endIndexExclusive) - 1; + float tmp; + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + /** + *

              + * Reverses the order of the given array in the given range. + *

              + * + *

              + * This method does nothing for a {@code null} input array. + *

              + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @since 3.2 + */ + public static void reverse(final int[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = startIndexInclusive < 0 ? 0 : startIndexInclusive; + int j = Math.min(array.length, endIndexExclusive) - 1; + int tmp; + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + /** + *

              + * Reverses the order of the given array in the given range. + *

              + * + *

              + * This method does nothing for a {@code null} input array. + *

              + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @since 3.2 + */ + public static void reverse(final long[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = startIndexInclusive < 0 ? 0 : startIndexInclusive; + int j = Math.min(array.length, endIndexExclusive) - 1; + long tmp; + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + /** + *

              + * Reverses the order of the given array in the given range. + *

              + * + *

              + * This method does nothing for a {@code null} input array. + *

              + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @since 3.2 + */ + public static void reverse(final Object[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = startIndexInclusive < 0 ? 0 : startIndexInclusive; + int j = Math.min(array.length, endIndexExclusive) - 1; + Object tmp; + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + /** + *

              + * Reverses the order of the given array in the given range. + *

              + * + *

              + * This method does nothing for a {@code null} input array. + *

              + * + * @param array + * the array to reverse, may be {@code null} + * @param startIndexInclusive + * the starting index. Undervalue (<0) is promoted to 0, overvalue (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @since 3.2 + */ + public static void reverse(final short[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = startIndexInclusive < 0 ? 0 : startIndexInclusive; + int j = Math.min(array.length, endIndexExclusive) - 1; + short tmp; + while (j > i) { + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + j--; + i++; + } + } + + // IndexOf search + // ---------------------------------------------------------------------- + + // Object IndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the index of the given object in the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param objectToFind the object to find, may be {@code null} + * @return the index of the object within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final Object[] array, final Object objectToFind) { + return indexOf(array, objectToFind, 0); + } + + /** + *

              Finds the index of the given object in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

              + * + * @param array the array to search through for the object, may be {@code null} + * @param objectToFind the object to find, may be {@code null} + * @param startIndex the index to start searching at + * @return the index of the object within the array starting at the index, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final Object[] array, final Object objectToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + if (objectToFind == null) { + for (int i = startIndex; i < array.length; i++) { + if (array[i] == null) { + return i; + } + } + } else if (array.getClass().getComponentType().isInstance(objectToFind)) { + for (int i = startIndex; i < array.length; i++) { + if (objectToFind.equals(array[i])) { + return i; + } + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the last index of the given object within the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to travers backwords looking for the object, may be {@code null} + * @param objectToFind the object to find, may be {@code null} + * @return the last index of the object within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final Object[] array, final Object objectToFind) { + return lastIndexOf(array, objectToFind, Integer.MAX_VALUE); + } + + /** + *

              Finds the last index of the given object in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than + * the array length will search from the end of the array.

              + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param objectToFind the object to find, may be {@code null} + * @param startIndex the start index to travers backwards from + * @return the last index of the object within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final Object[] array, final Object objectToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + if (objectToFind == null) { + for (int i = startIndex; i >= 0; i--) { + if (array[i] == null) { + return i; + } + } + } else if (array.getClass().getComponentType().isInstance(objectToFind)) { + for (int i = startIndex; i >= 0; i--) { + if (objectToFind.equals(array[i])) { + return i; + } + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Checks if the object is in the given array.

              + * + *

              The method returns {@code false} if a {@code null} array is passed in.

              + * + * @param array the array to search through + * @param objectToFind the object to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final Object[] array, final Object objectToFind) { + return indexOf(array, objectToFind) != INDEX_NOT_FOUND; + } + + // long IndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the index of the given value in the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final long[] array, final long valueToFind) { + return indexOf(array, valueToFind, 0); + } + + /** + *

              Finds the index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final long[] array, final long valueToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + for (int i = startIndex; i < array.length; i++) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the last index of the given value within the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to travers backwords looking for the object, may be {@code null} + * @param valueToFind the object to find + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final long[] array, final long valueToFind) { + return lastIndexOf(array, valueToFind, Integer.MAX_VALUE); + } + + /** + *

              Finds the last index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array.

              + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to travers backwards from + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final long[] array, final long valueToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Checks if the value is in the given array.

              + * + *

              The method returns {@code false} if a {@code null} array is passed in.

              + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final long[] array, final long valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + // int IndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the index of the given value in the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final int[] array, final int valueToFind) { + return indexOf(array, valueToFind, 0); + } + + /** + *

              Finds the index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final int[] array, final int valueToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + for (int i = startIndex; i < array.length; i++) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the last index of the given value within the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to travers backwords looking for the object, may be {@code null} + * @param valueToFind the object to find + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final int[] array, final int valueToFind) { + return lastIndexOf(array, valueToFind, Integer.MAX_VALUE); + } + + /** + *

              Finds the last index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array.

              + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to travers backwards from + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final int[] array, final int valueToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Checks if the value is in the given array.

              + * + *

              The method returns {@code false} if a {@code null} array is passed in.

              + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final int[] array, final int valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + // short IndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the index of the given value in the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final short[] array, final short valueToFind) { + return indexOf(array, valueToFind, 0); + } + + /** + *

              Finds the index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final short[] array, final short valueToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + for (int i = startIndex; i < array.length; i++) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the last index of the given value within the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to travers backwords looking for the object, may be {@code null} + * @param valueToFind the object to find + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final short[] array, final short valueToFind) { + return lastIndexOf(array, valueToFind, Integer.MAX_VALUE); + } + + /** + *

              Finds the last index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array.

              + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to travers backwards from + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final short[] array, final short valueToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Checks if the value is in the given array.

              + * + *

              The method returns {@code false} if a {@code null} array is passed in.

              + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final short[] array, final short valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + // char IndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the index of the given value in the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + * @since 2.1 + */ + public static int indexOf(final char[] array, final char valueToFind) { + return indexOf(array, valueToFind, 0); + } + + /** + *

              Finds the index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + * @since 2.1 + */ + public static int indexOf(final char[] array, final char valueToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + for (int i = startIndex; i < array.length; i++) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the last index of the given value within the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to travers backwords looking for the object, may be {@code null} + * @param valueToFind the object to find + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + * @since 2.1 + */ + public static int lastIndexOf(final char[] array, final char valueToFind) { + return lastIndexOf(array, valueToFind, Integer.MAX_VALUE); + } + + /** + *

              Finds the last index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array.

              + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to travers backwards from + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + * @since 2.1 + */ + public static int lastIndexOf(final char[] array, final char valueToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Checks if the value is in the given array.

              + * + *

              The method returns {@code false} if a {@code null} array is passed in.

              + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + * @since 2.1 + */ + public static boolean contains(final char[] array, final char valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + // byte IndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the index of the given value in the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final byte[] array, final byte valueToFind) { + return indexOf(array, valueToFind, 0); + } + + /** + *

              Finds the index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final byte[] array, final byte valueToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + for (int i = startIndex; i < array.length; i++) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the last index of the given value within the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to travers backwords looking for the object, may be {@code null} + * @param valueToFind the object to find + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final byte[] array, final byte valueToFind) { + return lastIndexOf(array, valueToFind, Integer.MAX_VALUE); + } + + /** + *

              Finds the last index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array.

              + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to travers backwards from + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final byte[] array, final byte valueToFind, int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Checks if the value is in the given array.

              + * + *

              The method returns {@code false} if a {@code null} array is passed in.

              + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final byte[] array, final byte valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + // double IndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the index of the given value in the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final double[] array, final double valueToFind) { + return indexOf(array, valueToFind, 0); + } + + /** + *

              Finds the index of the given value within a given tolerance in the array. + * This method will return the index of the first value which falls between the region + * defined by valueToFind - tolerance and valueToFind + tolerance.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param tolerance tolerance of the search + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final double[] array, final double valueToFind, final double tolerance) { + return indexOf(array, valueToFind, 0, tolerance); + } + + /** + *

              Finds the index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final double[] array, final double valueToFind, int startIndex) { + if (ArrayUtils.isEmpty(array)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + for (int i = startIndex; i < array.length; i++) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the index of the given value in the array starting at the given index. + * This method will return the index of the first value which falls between the region + * defined by valueToFind - tolerance and valueToFind + tolerance.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @param tolerance tolerance of the search + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final double[] array, final double valueToFind, int startIndex, final double tolerance) { + if (ArrayUtils.isEmpty(array)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + final double min = valueToFind - tolerance; + final double max = valueToFind + tolerance; + for (int i = startIndex; i < array.length; i++) { + if (array[i] >= min && array[i] <= max) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the last index of the given value within the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to travers backwords looking for the object, may be {@code null} + * @param valueToFind the object to find + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final double[] array, final double valueToFind) { + return lastIndexOf(array, valueToFind, Integer.MAX_VALUE); + } + + /** + *

              Finds the last index of the given value within a given tolerance in the array. + * This method will return the index of the last value which falls between the region + * defined by valueToFind - tolerance and valueToFind + tolerance.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param tolerance tolerance of the search + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final double[] array, final double valueToFind, final double tolerance) { + return lastIndexOf(array, valueToFind, Integer.MAX_VALUE, tolerance); + } + + /** + *

              Finds the last index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array.

              + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to travers backwards from + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final double[] array, final double valueToFind, int startIndex) { + if (ArrayUtils.isEmpty(array)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the last index of the given value in the array starting at the given index. + * This method will return the index of the last value which falls between the region + * defined by valueToFind - tolerance and valueToFind + tolerance.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array.

              + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to travers backwards from + * @param tolerance search for value within plus/minus this amount + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final double[] array, final double valueToFind, int startIndex, final double tolerance) { + if (ArrayUtils.isEmpty(array)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + final double min = valueToFind - tolerance; + final double max = valueToFind + tolerance; + for (int i = startIndex; i >= 0; i--) { + if (array[i] >= min && array[i] <= max) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Checks if the value is in the given array.

              + * + *

              The method returns {@code false} if a {@code null} array is passed in.

              + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final double[] array, final double valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + /** + *

              Checks if a value falling within the given tolerance is in the + * given array. If the array contains a value within the inclusive range + * defined by (value - tolerance) to (value + tolerance).

              + * + *

              The method returns {@code false} if a {@code null} array + * is passed in.

              + * + * @param array the array to search + * @param valueToFind the value to find + * @param tolerance the array contains the tolerance of the search + * @return true if value falling within tolerance is in array + */ + public static boolean contains(final double[] array, final double valueToFind, final double tolerance) { + return indexOf(array, valueToFind, 0, tolerance) != INDEX_NOT_FOUND; + } + + // float IndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the index of the given value in the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final float[] array, final float valueToFind) { + return indexOf(array, valueToFind, 0); + } + + /** + *

              Finds the index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final float[] array, final float valueToFind, int startIndex) { + if (ArrayUtils.isEmpty(array)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + for (int i = startIndex; i < array.length; i++) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the last index of the given value within the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to travers backwords looking for the object, may be {@code null} + * @param valueToFind the object to find + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final float[] array, final float valueToFind) { + return lastIndexOf(array, valueToFind, Integer.MAX_VALUE); + } + + /** + *

              Finds the last index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the + * array length will search from the end of the array.

              + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to travers backwards from + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final float[] array, final float valueToFind, int startIndex) { + if (ArrayUtils.isEmpty(array)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Checks if the value is in the given array.

              + * + *

              The method returns {@code false} if a {@code null} array is passed in.

              + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final float[] array, final float valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + // boolean IndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the index of the given value in the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int indexOf(final boolean[] array, final boolean valueToFind) { + return indexOf(array, valueToFind, 0); + } + + /** + *

              Finds the index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

              + * + * @param array the array to search through for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return the index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} + * array input + */ + public static int indexOf(final boolean[] array, final boolean valueToFind, int startIndex) { + if (ArrayUtils.isEmpty(array)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + startIndex = 0; + } + for (int i = startIndex; i < array.length; i++) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Finds the last index of the given value within the array.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) if + * {@code null} array input.

              + * + * @param array the array to travers backwords looking for the object, may be {@code null} + * @param valueToFind the object to find + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final boolean[] array, final boolean valueToFind) { + return lastIndexOf(array, valueToFind, Integer.MAX_VALUE); + } + + /** + *

              Finds the last index of the given value in the array starting at the given index.

              + * + *

              This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

              + * + *

              A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than + * the array length will search from the end of the array.

              + * + * @param array the array to traverse for looking for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the start index to travers backwards from + * @return the last index of the value within the array, + * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input + */ + public static int lastIndexOf(final boolean[] array, final boolean valueToFind, int startIndex) { + if (ArrayUtils.isEmpty(array)) { + return INDEX_NOT_FOUND; + } + if (startIndex < 0) { + return INDEX_NOT_FOUND; + } else if (startIndex >= array.length) { + startIndex = array.length - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Checks if the value is in the given array.

              + * + *

              The method returns {@code false} if a {@code null} array is passed in.

              + * + * @param array the array to search through + * @param valueToFind the value to find + * @return {@code true} if the array contains the object + */ + public static boolean contains(final boolean[] array, final boolean valueToFind) { + return indexOf(array, valueToFind) != INDEX_NOT_FOUND; + } + + // Primitive/Object array converters + // ---------------------------------------------------------------------- + + // Character array converters + // ---------------------------------------------------------------------- + /** + *

              Converts an array of object Characters to primitives.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Character} array, may be {@code null} + * @return a {@code char} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static char[] toPrimitive(final Character[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_CHAR_ARRAY; + } + final char[] result = new char[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].charValue(); + } + return result; + } + + /** + *

              Converts an array of object Character to primitives handling {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Character} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code char} array, {@code null} if null array input + */ + public static char[] toPrimitive(final Character[] array, final char valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_CHAR_ARRAY; + } + final char[] result = new char[array.length]; + for (int i = 0; i < array.length; i++) { + final Character b = array[i]; + result[i] = (b == null ? valueForNull : b.charValue()); + } + return result; + } + + /** + *

              Converts an array of primitive chars to objects.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code char} array + * @return a {@code Character} array, {@code null} if null array input + */ + public static Character[] toObject(final char[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_CHARACTER_OBJECT_ARRAY; + } + final Character[] result = new Character[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Character.valueOf(array[i]); + } + return result; + } + + // Long array converters + // ---------------------------------------------------------------------- + /** + *

              Converts an array of object Longs to primitives.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Long} array, may be {@code null} + * @return a {@code long} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static long[] toPrimitive(final Long[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_LONG_ARRAY; + } + final long[] result = new long[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].longValue(); + } + return result; + } + + /** + *

              Converts an array of object Long to primitives handling {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Long} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code long} array, {@code null} if null array input + */ + public static long[] toPrimitive(final Long[] array, final long valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_LONG_ARRAY; + } + final long[] result = new long[array.length]; + for (int i = 0; i < array.length; i++) { + final Long b = array[i]; + result[i] = (b == null ? valueForNull : b.longValue()); + } + return result; + } + + /** + *

              Converts an array of primitive longs to objects.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code long} array + * @return a {@code Long} array, {@code null} if null array input + */ + public static Long[] toObject(final long[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_LONG_OBJECT_ARRAY; + } + final Long[] result = new Long[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Long.valueOf(array[i]); + } + return result; + } + + // Int array converters + // ---------------------------------------------------------------------- + /** + *

              Converts an array of object Integers to primitives.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Integer} array, may be {@code null} + * @return an {@code int} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static int[] toPrimitive(final Integer[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_INT_ARRAY; + } + final int[] result = new int[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].intValue(); + } + return result; + } + + /** + *

              Converts an array of object Integer to primitives handling {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Integer} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return an {@code int} array, {@code null} if null array input + */ + public static int[] toPrimitive(final Integer[] array, final int valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_INT_ARRAY; + } + final int[] result = new int[array.length]; + for (int i = 0; i < array.length; i++) { + final Integer b = array[i]; + result[i] = (b == null ? valueForNull : b.intValue()); + } + return result; + } + + /** + *

              Converts an array of primitive ints to objects.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array an {@code int} array + * @return an {@code Integer} array, {@code null} if null array input + */ + public static Integer[] toObject(final int[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_INTEGER_OBJECT_ARRAY; + } + final Integer[] result = new Integer[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Integer.valueOf(array[i]); + } + return result; + } + + // Short array converters + // ---------------------------------------------------------------------- + /** + *

              Converts an array of object Shorts to primitives.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Short} array, may be {@code null} + * @return a {@code byte} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static short[] toPrimitive(final Short[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_SHORT_ARRAY; + } + final short[] result = new short[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].shortValue(); + } + return result; + } + + /** + *

              Converts an array of object Short to primitives handling {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Short} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code byte} array, {@code null} if null array input + */ + public static short[] toPrimitive(final Short[] array, final short valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_SHORT_ARRAY; + } + final short[] result = new short[array.length]; + for (int i = 0; i < array.length; i++) { + final Short b = array[i]; + result[i] = (b == null ? valueForNull : b.shortValue()); + } + return result; + } + + /** + *

              Converts an array of primitive shorts to objects.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code short} array + * @return a {@code Short} array, {@code null} if null array input + */ + public static Short[] toObject(final short[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_SHORT_OBJECT_ARRAY; + } + final Short[] result = new Short[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Short.valueOf(array[i]); + } + return result; + } + + // Byte array converters + // ---------------------------------------------------------------------- + /** + *

              Converts an array of object Bytes to primitives.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Byte} array, may be {@code null} + * @return a {@code byte} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static byte[] toPrimitive(final Byte[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BYTE_ARRAY; + } + final byte[] result = new byte[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].byteValue(); + } + return result; + } + + /** + *

              Converts an array of object Bytes to primitives handling {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Byte} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code byte} array, {@code null} if null array input + */ + public static byte[] toPrimitive(final Byte[] array, final byte valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BYTE_ARRAY; + } + final byte[] result = new byte[array.length]; + for (int i = 0; i < array.length; i++) { + final Byte b = array[i]; + result[i] = (b == null ? valueForNull : b.byteValue()); + } + return result; + } + + /** + *

              Converts an array of primitive bytes to objects.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code byte} array + * @return a {@code Byte} array, {@code null} if null array input + */ + public static Byte[] toObject(final byte[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BYTE_OBJECT_ARRAY; + } + final Byte[] result = new Byte[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Byte.valueOf(array[i]); + } + return result; + } + + // Double array converters + // ---------------------------------------------------------------------- + /** + *

              Converts an array of object Doubles to primitives.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Double} array, may be {@code null} + * @return a {@code double} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static double[] toPrimitive(final Double[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_DOUBLE_ARRAY; + } + final double[] result = new double[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].doubleValue(); + } + return result; + } + + /** + *

              Converts an array of object Doubles to primitives handling {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Double} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code double} array, {@code null} if null array input + */ + public static double[] toPrimitive(final Double[] array, final double valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_DOUBLE_ARRAY; + } + final double[] result = new double[array.length]; + for (int i = 0; i < array.length; i++) { + final Double b = array[i]; + result[i] = (b == null ? valueForNull : b.doubleValue()); + } + return result; + } + + /** + *

              Converts an array of primitive doubles to objects.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code double} array + * @return a {@code Double} array, {@code null} if null array input + */ + public static Double[] toObject(final double[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_DOUBLE_OBJECT_ARRAY; + } + final Double[] result = new Double[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Double.valueOf(array[i]); + } + return result; + } + + // Float array converters + // ---------------------------------------------------------------------- + /** + *

              Converts an array of object Floats to primitives.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Float} array, may be {@code null} + * @return a {@code float} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static float[] toPrimitive(final Float[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_FLOAT_ARRAY; + } + final float[] result = new float[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].floatValue(); + } + return result; + } + + /** + *

              Converts an array of object Floats to primitives handling {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Float} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code float} array, {@code null} if null array input + */ + public static float[] toPrimitive(final Float[] array, final float valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_FLOAT_ARRAY; + } + final float[] result = new float[array.length]; + for (int i = 0; i < array.length; i++) { + final Float b = array[i]; + result[i] = (b == null ? valueForNull : b.floatValue()); + } + return result; + } + + /** + *

              Converts an array of primitive floats to objects.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code float} array + * @return a {@code Float} array, {@code null} if null array input + */ + public static Float[] toObject(final float[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_FLOAT_OBJECT_ARRAY; + } + final Float[] result = new Float[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = Float.valueOf(array[i]); + } + return result; + } + + // Boolean array converters + // ---------------------------------------------------------------------- + /** + *

              Converts an array of object Booleans to primitives.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Boolean} array, may be {@code null} + * @return a {@code boolean} array, {@code null} if null array input + * @throws NullPointerException if array content is {@code null} + */ + public static boolean[] toPrimitive(final Boolean[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BOOLEAN_ARRAY; + } + final boolean[] result = new boolean[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i].booleanValue(); + } + return result; + } + + /** + *

              Converts an array of object Booleans to primitives handling {@code null}.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code Boolean} array, may be {@code null} + * @param valueForNull the value to insert if {@code null} found + * @return a {@code boolean} array, {@code null} if null array input + */ + public static boolean[] toPrimitive(final Boolean[] array, final boolean valueForNull) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BOOLEAN_ARRAY; + } + final boolean[] result = new boolean[array.length]; + for (int i = 0; i < array.length; i++) { + final Boolean b = array[i]; + result[i] = (b == null ? valueForNull : b.booleanValue()); + } + return result; + } + + /** + *

              Converts an array of primitive booleans to objects.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array a {@code boolean} array + * @return a {@code Boolean} array, {@code null} if null array input + */ + public static Boolean[] toObject(final boolean[] array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return EMPTY_BOOLEAN_OBJECT_ARRAY; + } + final Boolean[] result = new Boolean[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = (array[i] ? Boolean.TRUE : Boolean.FALSE); + } + return result; + } + + // ---------------------------------------------------------------------- + /** + *

              Checks if an array of Objects is empty or {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final Object[] array) { + return array == null || array.length == 0; + } + + /** + *

              Checks if an array of primitive longs is empty or {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final long[] array) { + return array == null || array.length == 0; + } + + /** + *

              Checks if an array of primitive ints is empty or {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final int[] array) { + return array == null || array.length == 0; + } + + /** + *

              Checks if an array of primitive shorts is empty or {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final short[] array) { + return array == null || array.length == 0; + } + + /** + *

              Checks if an array of primitive chars is empty or {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final char[] array) { + return array == null || array.length == 0; + } + + /** + *

              Checks if an array of primitive bytes is empty or {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final byte[] array) { + return array == null || array.length == 0; + } + + /** + *

              Checks if an array of primitive doubles is empty or {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final double[] array) { + return array == null || array.length == 0; + } + + /** + *

              Checks if an array of primitive floats is empty or {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final float[] array) { + return array == null || array.length == 0; + } + + /** + *

              Checks if an array of primitive booleans is empty or {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + * @since 2.1 + */ + public static boolean isEmpty(final boolean[] array) { + return array == null || array.length == 0; + } + + // ---------------------------------------------------------------------- + /** + *

              Checks if an array of Objects is not empty or not {@code null}.

              + * + * @param the component type of the array + * @param array the array to test + * @return {@code true} if the array is not empty or not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final T[] array) { + return (array != null && array.length != 0); + } + + /** + *

              Checks if an array of primitive longs is not empty or not {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is not empty or not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final long[] array) { + return (array != null && array.length != 0); + } + + /** + *

              Checks if an array of primitive ints is not empty or not {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is not empty or not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final int[] array) { + return (array != null && array.length != 0); + } + + /** + *

              Checks if an array of primitive shorts is not empty or not {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is not empty or not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final short[] array) { + return (array != null && array.length != 0); + } + + /** + *

              Checks if an array of primitive chars is not empty or not {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is not empty or not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final char[] array) { + return (array != null && array.length != 0); + } + + /** + *

              Checks if an array of primitive bytes is not empty or not {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is not empty or not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final byte[] array) { + return (array != null && array.length != 0); + } + + /** + *

              Checks if an array of primitive doubles is not empty or not {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is not empty or not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final double[] array) { + return (array != null && array.length != 0); + } + + /** + *

              Checks if an array of primitive floats is not empty or not {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is not empty or not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final float[] array) { + return (array != null && array.length != 0); + } + + /** + *

              Checks if an array of primitive booleans is not empty or not {@code null}.

              + * + * @param array the array to test + * @return {@code true} if the array is not empty or not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final boolean[] array) { + return (array != null && array.length != 0); + } + + /** + *

              Adds all the elements of the given arrays into a new array.

              + *

              The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array.

              + * + *
              +     * ArrayUtils.addAll(null, null)     = null
              +     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
              +     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
              +     * ArrayUtils.addAll([], [])         = []
              +     * ArrayUtils.addAll([null], [null]) = [null, null]
              +     * ArrayUtils.addAll(["a", "b", "c"], ["1", "2", "3"]) = ["a", "b", "c", "1", "2", "3"]
              +     * 
              + * + * @param the component type of the array + * @param array1 the first array whose elements are added to the new array, may be {@code null} + * @param array2 the second array whose elements are added to the new array, may be {@code null} + * @return The new array, {@code null} if both arrays are {@code null}. + * The type of the new array is the type of the first array, + * unless the first array is null, in which case the type is the same as the second array. + * @since 2.1 + * @throws IllegalArgumentException if the array types are incompatible + */ + public static T[] addAll(final T[] array1, final T... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final Class type1 = array1.getClass().getComponentType(); + @SuppressWarnings("unchecked") // OK, because array is of type T + final + T[] joinedArray = (T[]) Array.newInstance(type1, array1.length + array2.length); + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + try { + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + } catch (final ArrayStoreException ase) { + // Check if problem was due to incompatible types + /* + * We do this here, rather than before the copy because: + * - it would be a wasted check most of the time + * - safer, in case check turns out to be too strict + */ + final Class type2 = array2.getClass().getComponentType(); + if (!type1.isAssignableFrom(type2)){ + throw new IllegalArgumentException("Cannot store "+type2.getName()+" in an array of " + +type1.getName(), ase); + } + throw ase; // No, so rethrow original + } + return joinedArray; + } + + /** + *

              Adds all the elements of the given arrays into a new array.

              + *

              The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array.

              + * + *
              +     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
              +     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
              +     * ArrayUtils.addAll([], [])         = []
              +     * 
              + * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new boolean[] array. + * @since 2.1 + */ + public static boolean[] addAll(final boolean[] array1, final boolean... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final boolean[] joinedArray = new boolean[array1.length + array2.length]; + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + /** + *

              Adds all the elements of the given arrays into a new array.

              + *

              The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array.

              + * + *
              +     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
              +     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
              +     * ArrayUtils.addAll([], [])         = []
              +     * 
              + * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new char[] array. + * @since 2.1 + */ + public static char[] addAll(final char[] array1, final char... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final char[] joinedArray = new char[array1.length + array2.length]; + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + /** + *

              Adds all the elements of the given arrays into a new array.

              + *

              The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array.

              + * + *
              +     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
              +     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
              +     * ArrayUtils.addAll([], [])         = []
              +     * 
              + * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new byte[] array. + * @since 2.1 + */ + public static byte[] addAll(final byte[] array1, final byte... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final byte[] joinedArray = new byte[array1.length + array2.length]; + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + /** + *

              Adds all the elements of the given arrays into a new array.

              + *

              The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array.

              + * + *
              +     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
              +     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
              +     * ArrayUtils.addAll([], [])         = []
              +     * 
              + * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new short[] array. + * @since 2.1 + */ + public static short[] addAll(final short[] array1, final short... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final short[] joinedArray = new short[array1.length + array2.length]; + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + /** + *

              Adds all the elements of the given arrays into a new array.

              + *

              The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array.

              + * + *
              +     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
              +     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
              +     * ArrayUtils.addAll([], [])         = []
              +     * 
              + * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new int[] array. + * @since 2.1 + */ + public static int[] addAll(final int[] array1, final int... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final int[] joinedArray = new int[array1.length + array2.length]; + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + /** + *

              Adds all the elements of the given arrays into a new array.

              + *

              The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array.

              + * + *
              +     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
              +     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
              +     * ArrayUtils.addAll([], [])         = []
              +     * 
              + * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new long[] array. + * @since 2.1 + */ + public static long[] addAll(final long[] array1, final long... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final long[] joinedArray = new long[array1.length + array2.length]; + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + /** + *

              Adds all the elements of the given arrays into a new array.

              + *

              The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array.

              + * + *
              +     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
              +     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
              +     * ArrayUtils.addAll([], [])         = []
              +     * 
              + * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new float[] array. + * @since 2.1 + */ + public static float[] addAll(final float[] array1, final float... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final float[] joinedArray = new float[array1.length + array2.length]; + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + /** + *

              Adds all the elements of the given arrays into a new array.

              + *

              The new array contains all of the element of {@code array1} followed + * by all of the elements {@code array2}. When an array is returned, it is always + * a new array.

              + * + *
              +     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
              +     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
              +     * ArrayUtils.addAll([], [])         = []
              +     * 
              + * + * @param array1 the first array whose elements are added to the new array. + * @param array2 the second array whose elements are added to the new array. + * @return The new double[] array. + * @since 2.1 + */ + public static double[] addAll(final double[] array1, final double... array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } + final double[] joinedArray = new double[array1.length + array2.length]; + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + /** + *

              Copies the given array and adds the given element at the end of the new array.

              + * + *

              The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element, unless the element itself is null, + * in which case the return type is Object[]

              + * + *
              +     * ArrayUtils.add(null, null)      = [null]
              +     * ArrayUtils.add(null, "a")       = ["a"]
              +     * ArrayUtils.add(["a"], null)     = ["a", null]
              +     * ArrayUtils.add(["a"], "b")      = ["a", "b"]
              +     * ArrayUtils.add(["a", "b"], "c") = ["a", "b", "c"]
              +     * 
              + * + * @param the component type of the array + * @param array the array to "add" the element to, may be {@code null} + * @param element the object to add, may be {@code null} + * @return A new array containing the existing elements plus the new element + * The returned array type will be that of the input array (unless null), + * in which case it will have the same type as the element. + * If both are null, an IllegalArgumentException is thrown + * @since 2.1 + * @throws IllegalArgumentException if both arguments are null + */ + public static T[] add(final T[] array, final T element) { + Class type; + if (array != null){ + type = array.getClass().getComponentType(); + } else if (element != null) { + type = element.getClass(); + } else { + throw new IllegalArgumentException("Arguments cannot both be null"); + } + @SuppressWarnings("unchecked") // type must be T + final + T[] newArray = (T[]) copyArrayGrow1(array, type); + newArray[newArray.length - 1] = element; + return newArray; + } + + /** + *

              Copies the given array and adds the given element at the end of the new array.

              + * + *

              The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, true)          = [true]
              +     * ArrayUtils.add([true], false)       = [true, false]
              +     * ArrayUtils.add([true, false], true) = [true, false, true]
              +     * 
              + * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static boolean[] add(final boolean[] array, final boolean element) { + final boolean[] newArray = (boolean[])copyArrayGrow1(array, Boolean.TYPE); + newArray[newArray.length - 1] = element; + return newArray; + } + + /** + *

              Copies the given array and adds the given element at the end of the new array.

              + * + *

              The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, 0)   = [0]
              +     * ArrayUtils.add([1], 0)    = [1, 0]
              +     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
              +     * 
              + * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static byte[] add(final byte[] array, final byte element) { + final byte[] newArray = (byte[])copyArrayGrow1(array, Byte.TYPE); + newArray[newArray.length - 1] = element; + return newArray; + } + + /** + *

              Copies the given array and adds the given element at the end of the new array.

              + * + *

              The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, '0')       = ['0']
              +     * ArrayUtils.add(['1'], '0')      = ['1', '0']
              +     * ArrayUtils.add(['1', '0'], '1') = ['1', '0', '1']
              +     * 
              + * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static char[] add(final char[] array, final char element) { + final char[] newArray = (char[])copyArrayGrow1(array, Character.TYPE); + newArray[newArray.length - 1] = element; + return newArray; + } + + /** + *

              Copies the given array and adds the given element at the end of the new array.

              + * + *

              The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, 0)   = [0]
              +     * ArrayUtils.add([1], 0)    = [1, 0]
              +     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
              +     * 
              + * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static double[] add(final double[] array, final double element) { + final double[] newArray = (double[])copyArrayGrow1(array, Double.TYPE); + newArray[newArray.length - 1] = element; + return newArray; + } + + /** + *

              Copies the given array and adds the given element at the end of the new array.

              + * + *

              The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, 0)   = [0]
              +     * ArrayUtils.add([1], 0)    = [1, 0]
              +     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
              +     * 
              + * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static float[] add(final float[] array, final float element) { + final float[] newArray = (float[])copyArrayGrow1(array, Float.TYPE); + newArray[newArray.length - 1] = element; + return newArray; + } + + /** + *

              Copies the given array and adds the given element at the end of the new array.

              + * + *

              The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, 0)   = [0]
              +     * ArrayUtils.add([1], 0)    = [1, 0]
              +     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
              +     * 
              + * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static int[] add(final int[] array, final int element) { + final int[] newArray = (int[])copyArrayGrow1(array, Integer.TYPE); + newArray[newArray.length - 1] = element; + return newArray; + } + + /** + *

              Copies the given array and adds the given element at the end of the new array.

              + * + *

              The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, 0)   = [0]
              +     * ArrayUtils.add([1], 0)    = [1, 0]
              +     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
              +     * 
              + * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static long[] add(final long[] array, final long element) { + final long[] newArray = (long[])copyArrayGrow1(array, Long.TYPE); + newArray[newArray.length - 1] = element; + return newArray; + } + + /** + *

              Copies the given array and adds the given element at the end of the new array.

              + * + *

              The new array contains the same elements of the input + * array plus the given element in the last position. The component type of + * the new array is the same as that of the input array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, 0)   = [0]
              +     * ArrayUtils.add([1], 0)    = [1, 0]
              +     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
              +     * 
              + * + * @param array the array to copy and add the element to, may be {@code null} + * @param element the object to add at the last index of the new array + * @return A new array containing the existing elements plus the new element + * @since 2.1 + */ + public static short[] add(final short[] array, final short element) { + final short[] newArray = (short[])copyArrayGrow1(array, Short.TYPE); + newArray[newArray.length - 1] = element; + return newArray; + } + + /** + * Returns a copy of the given array of size 1 greater than the argument. + * The last value of the array is left to the default value. + * + * @param array The array to copy, must not be {@code null}. + * @param newArrayComponentType If {@code array} is {@code null}, create a + * size 1 array of this type. + * @return A new copy of the array of size 1 greater than the input. + */ + private static Object copyArrayGrow1(final Object array, final Class newArrayComponentType) { + if (array != null) { + final int arrayLength = Array.getLength(array); + final Object newArray = Array.newInstance(array.getClass().getComponentType(), arrayLength + 1); + System.arraycopy(array, 0, newArray, 0, arrayLength); + return newArray; + } + return Array.newInstance(newArrayComponentType, 1); + } + + /** + *

              Inserts the specified element at the specified position in the array. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, 0, null)      = [null]
              +     * ArrayUtils.add(null, 0, "a")       = ["a"]
              +     * ArrayUtils.add(["a"], 1, null)     = ["a", null]
              +     * ArrayUtils.add(["a"], 1, "b")      = ["a", "b"]
              +     * ArrayUtils.add(["a", "b"], 3, "c") = ["a", "b", "c"]
              +     * 
              + * + * @param the component type of the array + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index > array.length). + * @throws IllegalArgumentException if both array and element are null + */ + public static T[] add(final T[] array, final int index, final T element) { + Class clss = null; + if (array != null) { + clss = array.getClass().getComponentType(); + } else if (element != null) { + clss = element.getClass(); + } else { + throw new IllegalArgumentException("Array and element cannot both be null"); + } + @SuppressWarnings("unchecked") // the add method creates an array of type clss, which is type T + final T[] newArray = (T[]) add(array, index, element, clss); + return newArray; + } + + /** + *

              Inserts the specified element at the specified position in the array. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, 0, true)          = [true]
              +     * ArrayUtils.add([true], 0, false)       = [false, true]
              +     * ArrayUtils.add([false], 1, true)       = [false, true]
              +     * ArrayUtils.add([true, false], 1, true) = [true, true, false]
              +     * 
              + * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index > array.length). + */ + public static boolean[] add(final boolean[] array, final int index, final boolean element) { + return (boolean[]) add(array, index, Boolean.valueOf(element), Boolean.TYPE); + } + + /** + *

              Inserts the specified element at the specified position in the array. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add(null, 0, 'a')            = ['a']
              +     * ArrayUtils.add(['a'], 0, 'b')           = ['b', 'a']
              +     * ArrayUtils.add(['a', 'b'], 0, 'c')      = ['c', 'a', 'b']
              +     * ArrayUtils.add(['a', 'b'], 1, 'k')      = ['a', 'k', 'b']
              +     * ArrayUtils.add(['a', 'b', 'c'], 1, 't') = ['a', 't', 'b', 'c']
              +     * 
              + * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + */ + public static char[] add(final char[] array, final int index, final char element) { + return (char[]) add(array, index, Character.valueOf(element), Character.TYPE); + } + + /** + *

              Inserts the specified element at the specified position in the array. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add([1], 0, 2)         = [2, 1]
              +     * ArrayUtils.add([2, 6], 2, 3)      = [2, 6, 3]
              +     * ArrayUtils.add([2, 6], 0, 1)      = [1, 2, 6]
              +     * ArrayUtils.add([2, 6, 3], 2, 1)   = [2, 6, 1, 3]
              +     * 
              + * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + */ + public static byte[] add(final byte[] array, final int index, final byte element) { + return (byte[]) add(array, index, Byte.valueOf(element), Byte.TYPE); + } + + /** + *

              Inserts the specified element at the specified position in the array. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add([1], 0, 2)         = [2, 1]
              +     * ArrayUtils.add([2, 6], 2, 10)     = [2, 6, 10]
              +     * ArrayUtils.add([2, 6], 0, -4)     = [-4, 2, 6]
              +     * ArrayUtils.add([2, 6, 3], 2, 1)   = [2, 6, 1, 3]
              +     * 
              + * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + */ + public static short[] add(final short[] array, final int index, final short element) { + return (short[]) add(array, index, Short.valueOf(element), Short.TYPE); + } + + /** + *

              Inserts the specified element at the specified position in the array. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add([1], 0, 2)         = [2, 1]
              +     * ArrayUtils.add([2, 6], 2, 10)     = [2, 6, 10]
              +     * ArrayUtils.add([2, 6], 0, -4)     = [-4, 2, 6]
              +     * ArrayUtils.add([2, 6, 3], 2, 1)   = [2, 6, 1, 3]
              +     * 
              + * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + */ + public static int[] add(final int[] array, final int index, final int element) { + return (int[]) add(array, index, Integer.valueOf(element), Integer.TYPE); + } + + /** + *

              Inserts the specified element at the specified position in the array. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add([1L], 0, 2L)           = [2L, 1L]
              +     * ArrayUtils.add([2L, 6L], 2, 10L)      = [2L, 6L, 10L]
              +     * ArrayUtils.add([2L, 6L], 0, -4L)      = [-4L, 2L, 6L]
              +     * ArrayUtils.add([2L, 6L, 3L], 2, 1L)   = [2L, 6L, 1L, 3L]
              +     * 
              + * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + */ + public static long[] add(final long[] array, final int index, final long element) { + return (long[]) add(array, index, Long.valueOf(element), Long.TYPE); + } + + /** + *

              Inserts the specified element at the specified position in the array. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add([1.1f], 0, 2.2f)               = [2.2f, 1.1f]
              +     * ArrayUtils.add([2.3f, 6.4f], 2, 10.5f)        = [2.3f, 6.4f, 10.5f]
              +     * ArrayUtils.add([2.6f, 6.7f], 0, -4.8f)        = [-4.8f, 2.6f, 6.7f]
              +     * ArrayUtils.add([2.9f, 6.0f, 0.3f], 2, 1.0f)   = [2.9f, 6.0f, 1.0f, 0.3f]
              +     * 
              + * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + */ + public static float[] add(final float[] array, final int index, final float element) { + return (float[]) add(array, index, Float.valueOf(element), Float.TYPE); + } + + /** + *

              Inserts the specified element at the specified position in the array. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array plus the given element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

              + * + *
              +     * ArrayUtils.add([1.1], 0, 2.2)              = [2.2, 1.1]
              +     * ArrayUtils.add([2.3, 6.4], 2, 10.5)        = [2.3, 6.4, 10.5]
              +     * ArrayUtils.add([2.6, 6.7], 0, -4.8)        = [-4.8, 2.6, 6.7]
              +     * ArrayUtils.add([2.9, 6.0, 0.3], 2, 1.0)    = [2.9, 6.0, 1.0, 0.3]
              +     * 
              + * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @return A new array containing the existing elements and the new element + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > array.length). + */ + public static double[] add(final double[] array, final int index, final double element) { + return (double[]) add(array, index, Double.valueOf(element), Double.TYPE); + } + + /** + * Underlying implementation of add(array, index, element) methods. + * The last parameter is the class, which may not equal element.getClass + * for primitives. + * + * @param array the array to add the element to, may be {@code null} + * @param index the position of the new object + * @param element the object to add + * @param clss the type of the element being added + * @return A new array containing the existing elements and the new element + */ + private static Object add(final Object array, final int index, final Object element, final Class clss) { + if (array == null) { + if (index != 0) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: 0"); + } + final Object joinedArray = Array.newInstance(clss, 1); + Array.set(joinedArray, 0, element); + return joinedArray; + } + final int length = Array.getLength(array); + if (index > length || index < 0) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length); + } + final Object result = Array.newInstance(clss, length + 1); + System.arraycopy(array, 0, result, 0, index); + Array.set(result, index, element); + if (index < length) { + System.arraycopy(array, index, result, index + 1, length - index); + } + return result; + } + + /** + *

              Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.remove(["a"], 0)           = []
              +     * ArrayUtils.remove(["a", "b"], 0)      = ["b"]
              +     * ArrayUtils.remove(["a", "b"], 1)      = ["a"]
              +     * ArrayUtils.remove(["a", "b", "c"], 1) = ["a", "c"]
              +     * 
              + * + * @param the component type of the array + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + @SuppressWarnings("unchecked") // remove() always creates an array of the same type as its input + public static T[] remove(final T[] array, final int index) { + return (T[]) remove((Object) array, index); + } + + /** + *

              Removes the first occurrence of the specified element from the + * specified array. All subsequent elements are shifted to the left + * (subtracts one from their indices). If the array doesn't contains + * such an element, no elements are removed from the array.

              + * + *

              This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *
              +     * ArrayUtils.removeElement(null, "a")            = null
              +     * ArrayUtils.removeElement([], "a")              = []
              +     * ArrayUtils.removeElement(["a"], "b")           = ["a"]
              +     * ArrayUtils.removeElement(["a", "b"], "a")      = ["b"]
              +     * ArrayUtils.removeElement(["a", "b", "a"], "a") = ["b", "a"]
              +     * 
              + * + * @param the component type of the array + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static T[] removeElement(final T[] array, final Object element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return remove(array, index); + } + + /** + *

              Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.remove([true], 0)              = []
              +     * ArrayUtils.remove([true, false], 0)       = [false]
              +     * ArrayUtils.remove([true, false], 1)       = [true]
              +     * ArrayUtils.remove([true, true, false], 1) = [true, false]
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static boolean[] remove(final boolean[] array, final int index) { + return (boolean[]) remove((Object) array, index); + } + + /** + *

              Removes the first occurrence of the specified element from the + * specified array. All subsequent elements are shifted to the left + * (subtracts one from their indices). If the array doesn't contains + * such an element, no elements are removed from the array.

              + * + *

              This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *
              +     * ArrayUtils.removeElement(null, true)                = null
              +     * ArrayUtils.removeElement([], true)                  = []
              +     * ArrayUtils.removeElement([true], false)             = [true]
              +     * ArrayUtils.removeElement([true, false], false)      = [true]
              +     * ArrayUtils.removeElement([true, false, true], true) = [false, true]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static boolean[] removeElement(final boolean[] array, final boolean element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return remove(array, index); + } + + /** + *

              Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.remove([1], 0)          = []
              +     * ArrayUtils.remove([1, 0], 0)       = [0]
              +     * ArrayUtils.remove([1, 0], 1)       = [1]
              +     * ArrayUtils.remove([1, 0, 1], 1)    = [1, 1]
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static byte[] remove(final byte[] array, final int index) { + return (byte[]) remove((Object) array, index); + } + + /** + *

              Removes the first occurrence of the specified element from the + * specified array. All subsequent elements are shifted to the left + * (subtracts one from their indices). If the array doesn't contains + * such an element, no elements are removed from the array.

              + * + *

              This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *
              +     * ArrayUtils.removeElement(null, 1)        = null
              +     * ArrayUtils.removeElement([], 1)          = []
              +     * ArrayUtils.removeElement([1], 0)         = [1]
              +     * ArrayUtils.removeElement([1, 0], 0)      = [1]
              +     * ArrayUtils.removeElement([1, 0, 1], 1)   = [0, 1]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static byte[] removeElement(final byte[] array, final byte element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return remove(array, index); + } + + /** + *

              Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.remove(['a'], 0)           = []
              +     * ArrayUtils.remove(['a', 'b'], 0)      = ['b']
              +     * ArrayUtils.remove(['a', 'b'], 1)      = ['a']
              +     * ArrayUtils.remove(['a', 'b', 'c'], 1) = ['a', 'c']
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static char[] remove(final char[] array, final int index) { + return (char[]) remove((Object) array, index); + } + + /** + *

              Removes the first occurrence of the specified element from the + * specified array. All subsequent elements are shifted to the left + * (subtracts one from their indices). If the array doesn't contains + * such an element, no elements are removed from the array.

              + * + *

              This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *
              +     * ArrayUtils.removeElement(null, 'a')            = null
              +     * ArrayUtils.removeElement([], 'a')              = []
              +     * ArrayUtils.removeElement(['a'], 'b')           = ['a']
              +     * ArrayUtils.removeElement(['a', 'b'], 'a')      = ['b']
              +     * ArrayUtils.removeElement(['a', 'b', 'a'], 'a') = ['b', 'a']
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static char[] removeElement(final char[] array, final char element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return remove(array, index); + } + + /** + *

              Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.remove([1.1], 0)           = []
              +     * ArrayUtils.remove([2.5, 6.0], 0)      = [6.0]
              +     * ArrayUtils.remove([2.5, 6.0], 1)      = [2.5]
              +     * ArrayUtils.remove([2.5, 6.0, 3.8], 1) = [2.5, 3.8]
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static double[] remove(final double[] array, final int index) { + return (double[]) remove((Object) array, index); + } + + /** + *

              Removes the first occurrence of the specified element from the + * specified array. All subsequent elements are shifted to the left + * (subtracts one from their indices). If the array doesn't contains + * such an element, no elements are removed from the array.

              + * + *

              This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *
              +     * ArrayUtils.removeElement(null, 1.1)            = null
              +     * ArrayUtils.removeElement([], 1.1)              = []
              +     * ArrayUtils.removeElement([1.1], 1.2)           = [1.1]
              +     * ArrayUtils.removeElement([1.1, 2.3], 1.1)      = [2.3]
              +     * ArrayUtils.removeElement([1.1, 2.3, 1.1], 1.1) = [2.3, 1.1]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static double[] removeElement(final double[] array, final double element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return remove(array, index); + } + + /** + *

              Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.remove([1.1], 0)           = []
              +     * ArrayUtils.remove([2.5, 6.0], 0)      = [6.0]
              +     * ArrayUtils.remove([2.5, 6.0], 1)      = [2.5]
              +     * ArrayUtils.remove([2.5, 6.0, 3.8], 1) = [2.5, 3.8]
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static float[] remove(final float[] array, final int index) { + return (float[]) remove((Object) array, index); + } + + /** + *

              Removes the first occurrence of the specified element from the + * specified array. All subsequent elements are shifted to the left + * (subtracts one from their indices). If the array doesn't contains + * such an element, no elements are removed from the array.

              + * + *

              This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *
              +     * ArrayUtils.removeElement(null, 1.1)            = null
              +     * ArrayUtils.removeElement([], 1.1)              = []
              +     * ArrayUtils.removeElement([1.1], 1.2)           = [1.1]
              +     * ArrayUtils.removeElement([1.1, 2.3], 1.1)      = [2.3]
              +     * ArrayUtils.removeElement([1.1, 2.3, 1.1], 1.1) = [2.3, 1.1]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static float[] removeElement(final float[] array, final float element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return remove(array, index); + } + + /** + *

              Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.remove([1], 0)         = []
              +     * ArrayUtils.remove([2, 6], 0)      = [6]
              +     * ArrayUtils.remove([2, 6], 1)      = [2]
              +     * ArrayUtils.remove([2, 6, 3], 1)   = [2, 3]
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static int[] remove(final int[] array, final int index) { + return (int[]) remove((Object) array, index); + } + + /** + *

              Removes the first occurrence of the specified element from the + * specified array. All subsequent elements are shifted to the left + * (subtracts one from their indices). If the array doesn't contains + * such an element, no elements are removed from the array.

              + * + *

              This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *
              +     * ArrayUtils.removeElement(null, 1)      = null
              +     * ArrayUtils.removeElement([], 1)        = []
              +     * ArrayUtils.removeElement([1], 2)       = [1]
              +     * ArrayUtils.removeElement([1, 3], 1)    = [3]
              +     * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static int[] removeElement(final int[] array, final int element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return remove(array, index); + } + + /** + *

              Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.remove([1], 0)         = []
              +     * ArrayUtils.remove([2, 6], 0)      = [6]
              +     * ArrayUtils.remove([2, 6], 1)      = [2]
              +     * ArrayUtils.remove([2, 6, 3], 1)   = [2, 3]
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static long[] remove(final long[] array, final int index) { + return (long[]) remove((Object) array, index); + } + + /** + *

              Removes the first occurrence of the specified element from the + * specified array. All subsequent elements are shifted to the left + * (subtracts one from their indices). If the array doesn't contains + * such an element, no elements are removed from the array.

              + * + *

              This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *
              +     * ArrayUtils.removeElement(null, 1)      = null
              +     * ArrayUtils.removeElement([], 1)        = []
              +     * ArrayUtils.removeElement([1], 2)       = [1]
              +     * ArrayUtils.removeElement([1, 3], 1)    = [3]
              +     * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static long[] removeElement(final long[] array, final long element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return remove(array, index); + } + + /** + *

              Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.remove([1], 0)         = []
              +     * ArrayUtils.remove([2, 6], 0)      = [6]
              +     * ArrayUtils.remove([2, 6], 1)      = [2]
              +     * ArrayUtils.remove([2, 6, 3], 1)   = [2, 3]
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + public static short[] remove(final short[] array, final int index) { + return (short[]) remove((Object) array, index); + } + + /** + *

              Removes the first occurrence of the specified element from the + * specified array. All subsequent elements are shifted to the left + * (subtracts one from their indices). If the array doesn't contains + * such an element, no elements are removed from the array.

              + * + *

              This method returns a new array with the same elements of the input + * array except the first occurrence of the specified element. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *
              +     * ArrayUtils.removeElement(null, 1)      = null
              +     * ArrayUtils.removeElement([], 1)        = []
              +     * ArrayUtils.removeElement([1], 2)       = [1]
              +     * ArrayUtils.removeElement([1, 3], 1)    = [3]
              +     * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param element the element to be removed + * @return A new array containing the existing elements except the first + * occurrence of the specified element. + * @since 2.1 + */ + public static short[] removeElement(final short[] array, final short element) { + final int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return remove(array, index); + } + + /** + *

              Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

              + * + *

              This method returns a new array with the same elements of the input + * array except the element on the specified position. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + * @param array the array to remove the element from, may not be {@code null} + * @param index the position of the element to be removed + * @return A new array containing the existing elements except the element + * at the specified position. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 2.1 + */ + private static Object remove(final Object array, final int index) { + final int length = getLength(array); + if (index < 0 || index >= length) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length); + } + + final Object result = Array.newInstance(array.getClass().getComponentType(), length - 1); + System.arraycopy(array, 0, result, 0, index); + if (index < length - 1) { + System.arraycopy(array, index + 1, result, index, length - index - 1); + } + + return result; + } + + /** + *

              Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

              + * + *

              This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.removeAll(["a", "b", "c"], 0, 2) = ["b"]
              +     * ArrayUtils.removeAll(["a", "b", "c"], 1, 2) = ["a"]
              +     * 
              + * + * @param the component type of the array + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + @SuppressWarnings("unchecked") // removeAll() always creates an array of the same type as its input + public static T[] removeAll(final T[] array, final int... indices) { + return (T[]) removeAll((Object) array, clone(indices)); + } + + /** + *

              Removes occurrences of specified elements, in specified quantities, + * from the specified array. All subsequent elements are shifted left. + * For any element-to-be-removed specified in greater quantities than + * contained in the original array, no change occurs beyond the + * removal of the existing matching items.

              + * + *

              This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array.

              + * + *
              +     * ArrayUtils.removeElements(null, "a", "b")            = null
              +     * ArrayUtils.removeElements([], "a", "b")              = []
              +     * ArrayUtils.removeElements(["a"], "b", "c")           = ["a"]
              +     * ArrayUtils.removeElements(["a", "b"], "a", "c")      = ["b"]
              +     * ArrayUtils.removeElements(["a", "b", "a"], "a")      = ["b", "a"]
              +     * ArrayUtils.removeElements(["a", "b", "a"], "a", "a") = ["b"]
              +     * 
              + * + * @param the component type of the array + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static T[] removeElements(final T[] array, final T... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap occurrences = new HashMap(values.length); + for (final T v : values) { + final MutableInt count = occurrences.get(v); + if (count == null) { + occurrences.put(v, new MutableInt(1)); + } else { + count.increment(); + } + } + final BitSet toRemove = new BitSet(); + for (final Map.Entry e : occurrences.entrySet()) { + final T v = e.getKey(); + int found = 0; + for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) { + found = indexOf(array, v, found); + if (found < 0) { + break; + } + toRemove.set(found++); + } + } + @SuppressWarnings("unchecked") // removeAll() always creates an array of the same type as its input + final + T[] result = (T[]) removeAll(array, toRemove); + return result; + } + + /** + *

              Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

              + * + *

              This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.removeAll([1], 0)             = []
              +     * ArrayUtils.removeAll([2, 6], 0)          = [6]
              +     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
              +     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static byte[] removeAll(final byte[] array, final int... indices) { + return (byte[]) removeAll((Object) array, clone(indices)); + } + + /** + *

              Removes occurrences of specified elements, in specified quantities, + * from the specified array. All subsequent elements are shifted left. + * For any element-to-be-removed specified in greater quantities than + * contained in the original array, no change occurs beyond the + * removal of the existing matching items.

              + * + *

              This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array.

              + * + *
              +     * ArrayUtils.removeElements(null, 1, 2)      = null
              +     * ArrayUtils.removeElements([], 1, 2)        = []
              +     * ArrayUtils.removeElements([1], 2, 3)       = [1]
              +     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
              +     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
              +     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static byte[] removeElements(final byte[] array, final byte... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final Map occurrences = new HashMap(values.length); + for (final byte v : values) { + final Byte boxed = Byte.valueOf(v); + final MutableInt count = occurrences.get(boxed); + if (count == null) { + occurrences.put(boxed, new MutableInt(1)); + } else { + count.increment(); + } + } + final BitSet toRemove = new BitSet(); + for (final Map.Entry e : occurrences.entrySet()) { + final Byte v = e.getKey(); + int found = 0; + for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) { + found = indexOf(array, v.byteValue(), found); + if (found < 0) { + break; + } + toRemove.set(found++); + } + } + return (byte[]) removeAll(array, toRemove); + } + + /** + *

              Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

              + * + *

              This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.removeAll([1], 0)             = []
              +     * ArrayUtils.removeAll([2, 6], 0)          = [6]
              +     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
              +     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static short[] removeAll(final short[] array, final int... indices) { + return (short[]) removeAll((Object) array, clone(indices)); + } + + /** + *

              Removes occurrences of specified elements, in specified quantities, + * from the specified array. All subsequent elements are shifted left. + * For any element-to-be-removed specified in greater quantities than + * contained in the original array, no change occurs beyond the + * removal of the existing matching items.

              + * + *

              This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array.

              + * + *
              +     * ArrayUtils.removeElements(null, 1, 2)      = null
              +     * ArrayUtils.removeElements([], 1, 2)        = []
              +     * ArrayUtils.removeElements([1], 2, 3)       = [1]
              +     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
              +     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
              +     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static short[] removeElements(final short[] array, final short... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap occurrences = new HashMap(values.length); + for (final short v : values) { + final Short boxed = Short.valueOf(v); + final MutableInt count = occurrences.get(boxed); + if (count == null) { + occurrences.put(boxed, new MutableInt(1)); + } else { + count.increment(); + } + } + final BitSet toRemove = new BitSet(); + for (final Map.Entry e : occurrences.entrySet()) { + final Short v = e.getKey(); + int found = 0; + for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) { + found = indexOf(array, v.shortValue(), found); + if (found < 0) { + break; + } + toRemove.set(found++); + } + } + return (short[]) removeAll(array, toRemove); + } + + /** + *

              Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

              + * + *

              This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.removeAll([1], 0)             = []
              +     * ArrayUtils.removeAll([2, 6], 0)          = [6]
              +     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
              +     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static int[] removeAll(final int[] array, final int... indices) { + return (int[]) removeAll((Object) array, clone(indices)); + } + + /** + *

              Removes occurrences of specified elements, in specified quantities, + * from the specified array. All subsequent elements are shifted left. + * For any element-to-be-removed specified in greater quantities than + * contained in the original array, no change occurs beyond the + * removal of the existing matching items.

              + * + *

              This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array.

              + * + *
              +     * ArrayUtils.removeElements(null, 1, 2)      = null
              +     * ArrayUtils.removeElements([], 1, 2)        = []
              +     * ArrayUtils.removeElements([1], 2, 3)       = [1]
              +     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
              +     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
              +     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static int[] removeElements(final int[] array, final int... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap occurrences = new HashMap(values.length); + for (final int v : values) { + final Integer boxed = Integer.valueOf(v); + final MutableInt count = occurrences.get(boxed); + if (count == null) { + occurrences.put(boxed, new MutableInt(1)); + } else { + count.increment(); + } + } + final BitSet toRemove = new BitSet(); + for (final Map.Entry e : occurrences.entrySet()) { + final Integer v = e.getKey(); + int found = 0; + for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) { + found = indexOf(array, v.intValue(), found); + if (found < 0) { + break; + } + toRemove.set(found++); + } + } + return (int[]) removeAll(array, toRemove); + } + + /** + *

              Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

              + * + *

              This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.removeAll([1], 0)             = []
              +     * ArrayUtils.removeAll([2, 6], 0)          = [6]
              +     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
              +     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static char[] removeAll(final char[] array, final int... indices) { + return (char[]) removeAll((Object) array, clone(indices)); + } + + /** + *

              Removes occurrences of specified elements, in specified quantities, + * from the specified array. All subsequent elements are shifted left. + * For any element-to-be-removed specified in greater quantities than + * contained in the original array, no change occurs beyond the + * removal of the existing matching items.

              + * + *

              This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array.

              + * + *
              +     * ArrayUtils.removeElements(null, 1, 2)      = null
              +     * ArrayUtils.removeElements([], 1, 2)        = []
              +     * ArrayUtils.removeElements([1], 2, 3)       = [1]
              +     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
              +     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
              +     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static char[] removeElements(final char[] array, final char... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap occurrences = new HashMap(values.length); + for (final char v : values) { + final Character boxed = Character.valueOf(v); + final MutableInt count = occurrences.get(boxed); + if (count == null) { + occurrences.put(boxed, new MutableInt(1)); + } else { + count.increment(); + } + } + final BitSet toRemove = new BitSet(); + for (final Map.Entry e : occurrences.entrySet()) { + final Character v = e.getKey(); + int found = 0; + for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) { + found = indexOf(array, v.charValue(), found); + if (found < 0) { + break; + } + toRemove.set(found++); + } + } + return (char[]) removeAll(array, toRemove); + } + + /** + *

              Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

              + * + *

              This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.removeAll([1], 0)             = []
              +     * ArrayUtils.removeAll([2, 6], 0)          = [6]
              +     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
              +     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static long[] removeAll(final long[] array, final int... indices) { + return (long[]) removeAll((Object) array, clone(indices)); + } + + /** + *

              Removes occurrences of specified elements, in specified quantities, + * from the specified array. All subsequent elements are shifted left. + * For any element-to-be-removed specified in greater quantities than + * contained in the original array, no change occurs beyond the + * removal of the existing matching items.

              + * + *

              This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array.

              + * + *
              +     * ArrayUtils.removeElements(null, 1, 2)      = null
              +     * ArrayUtils.removeElements([], 1, 2)        = []
              +     * ArrayUtils.removeElements([1], 2, 3)       = [1]
              +     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
              +     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
              +     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static long[] removeElements(final long[] array, final long... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap occurrences = new HashMap(values.length); + for (final long v : values) { + final Long boxed = Long.valueOf(v); + final MutableInt count = occurrences.get(boxed); + if (count == null) { + occurrences.put(boxed, new MutableInt(1)); + } else { + count.increment(); + } + } + final BitSet toRemove = new BitSet(); + for (final Map.Entry e : occurrences.entrySet()) { + final Long v = e.getKey(); + int found = 0; + for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) { + found = indexOf(array, v.longValue(), found); + if (found < 0) { + break; + } + toRemove.set(found++); + } + } + return (long[]) removeAll(array, toRemove); + } + + /** + *

              Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

              + * + *

              This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.removeAll([1], 0)             = []
              +     * ArrayUtils.removeAll([2, 6], 0)          = [6]
              +     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
              +     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static float[] removeAll(final float[] array, final int... indices) { + return (float[]) removeAll((Object) array, clone(indices)); + } + + /** + *

              Removes occurrences of specified elements, in specified quantities, + * from the specified array. All subsequent elements are shifted left. + * For any element-to-be-removed specified in greater quantities than + * contained in the original array, no change occurs beyond the + * removal of the existing matching items.

              + * + *

              This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array.

              + * + *
              +     * ArrayUtils.removeElements(null, 1, 2)      = null
              +     * ArrayUtils.removeElements([], 1, 2)        = []
              +     * ArrayUtils.removeElements([1], 2, 3)       = [1]
              +     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
              +     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
              +     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static float[] removeElements(final float[] array, final float... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap occurrences = new HashMap(values.length); + for (final float v : values) { + final Float boxed = Float.valueOf(v); + final MutableInt count = occurrences.get(boxed); + if (count == null) { + occurrences.put(boxed, new MutableInt(1)); + } else { + count.increment(); + } + } + final BitSet toRemove = new BitSet(); + for (final Map.Entry e : occurrences.entrySet()) { + final Float v = e.getKey(); + int found = 0; + for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) { + found = indexOf(array, v.floatValue(), found); + if (found < 0) { + break; + } + toRemove.set(found++); + } + } + return (float[]) removeAll(array, toRemove); + } + + /** + *

              Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

              + * + *

              This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.removeAll([1], 0)             = []
              +     * ArrayUtils.removeAll([2, 6], 0)          = [6]
              +     * ArrayUtils.removeAll([2, 6], 0, 1)       = []
              +     * ArrayUtils.removeAll([2, 6, 3], 1, 2)    = [2]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 2)    = [6]
              +     * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static double[] removeAll(final double[] array, final int... indices) { + return (double[]) removeAll((Object) array, clone(indices)); + } + + /** + *

              Removes occurrences of specified elements, in specified quantities, + * from the specified array. All subsequent elements are shifted left. + * For any element-to-be-removed specified in greater quantities than + * contained in the original array, no change occurs beyond the + * removal of the existing matching items.

              + * + *

              This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array.

              + * + *
              +     * ArrayUtils.removeElements(null, 1, 2)      = null
              +     * ArrayUtils.removeElements([], 1, 2)        = []
              +     * ArrayUtils.removeElements([1], 2, 3)       = [1]
              +     * ArrayUtils.removeElements([1, 3], 1, 2)    = [3]
              +     * ArrayUtils.removeElements([1, 3, 1], 1)    = [3, 1]
              +     * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static double[] removeElements(final double[] array, final double... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap occurrences = new HashMap(values.length); + for (final double v : values) { + final Double boxed = Double.valueOf(v); + final MutableInt count = occurrences.get(boxed); + if (count == null) { + occurrences.put(boxed, new MutableInt(1)); + } else { + count.increment(); + } + } + final BitSet toRemove = new BitSet(); + for (final Map.Entry e : occurrences.entrySet()) { + final Double v = e.getKey(); + int found = 0; + for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) { + found = indexOf(array, v.doubleValue(), found); + if (found < 0) { + break; + } + toRemove.set(found++); + } + } + return (double[]) removeAll(array, toRemove); + } + + /** + *

              Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

              + * + *

              This method returns a new array with the same elements of the input + * array except those at the specified positions. The component + * type of the returned array is always the same as that of the input + * array.

              + * + *

              If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

              + * + *
              +     * ArrayUtils.removeAll([true, false, true], 0, 2) = [false]
              +     * ArrayUtils.removeAll([true, false, true], 1, 2) = [true]
              +     * 
              + * + * @param array the array to remove the element from, may not be {@code null} + * @param indices the positions of the elements to be removed + * @return A new array containing the existing elements except those + * at the specified positions. + * @throws IndexOutOfBoundsException if any index is out of range + * (index < 0 || index >= array.length), or if the array is {@code null}. + * @since 3.0.1 + */ + public static boolean[] removeAll(final boolean[] array, final int... indices) { + return (boolean[]) removeAll((Object) array, clone(indices)); + } + + /** + *

              Removes occurrences of specified elements, in specified quantities, + * from the specified array. All subsequent elements are shifted left. + * For any element-to-be-removed specified in greater quantities than + * contained in the original array, no change occurs beyond the + * removal of the existing matching items.

              + * + *

              This method returns a new array with the same elements of the input + * array except for the earliest-encountered occurrences of the specified + * elements. The component type of the returned array is always the same + * as that of the input array.

              + * + *
              +     * ArrayUtils.removeElements(null, true, false)               = null
              +     * ArrayUtils.removeElements([], true, false)                 = []
              +     * ArrayUtils.removeElements([true], false, false)            = [true]
              +     * ArrayUtils.removeElements([true, false], true, true)       = [false]
              +     * ArrayUtils.removeElements([true, false, true], true)       = [false, true]
              +     * ArrayUtils.removeElements([true, false, true], true, true) = [false]
              +     * 
              + * + * @param array the array to remove the element from, may be {@code null} + * @param values the elements to be removed + * @return A new array containing the existing elements except the + * earliest-encountered occurrences of the specified elements. + * @since 3.0.1 + */ + public static boolean[] removeElements(final boolean[] array, final boolean... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + final HashMap occurrences = new HashMap(2); // only two possible values here + for (final boolean v : values) { + final Boolean boxed = Boolean.valueOf(v); + final MutableInt count = occurrences.get(boxed); + if (count == null) { + occurrences.put(boxed, new MutableInt(1)); + } else { + count.increment(); + } + } + final BitSet toRemove = new BitSet(); + for (final Map.Entry e : occurrences.entrySet()) { + final Boolean v = e.getKey(); + int found = 0; + for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) { + found = indexOf(array, v.booleanValue(), found); + if (found < 0) { + break; + } + toRemove.set(found++); + } + } + return (boolean[]) removeAll(array, toRemove); + } + + /** + * Removes multiple array elements specified by index. + * @param array source + * @param indices to remove, WILL BE SORTED--so only clones of user-owned arrays! + * @return new array of same type minus elements specified by unique values of {@code indices} + * @since 3.0.1 + */ + // package protected for access by unit tests + static Object removeAll(final Object array, final int... indices) { + final int length = getLength(array); + int diff = 0; // number of distinct indexes, i.e. number of entries that will be removed + + if (isNotEmpty(indices)) { + Arrays.sort(indices); + + int i = indices.length; + int prevIndex = length; + while (--i >= 0) { + final int index = indices[i]; + if (index < 0 || index >= length) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length); + } + if (index >= prevIndex) { + continue; + } + diff++; + prevIndex = index; + } + } + final Object result = Array.newInstance(array.getClass().getComponentType(), length - diff); + if (diff < length) { + int end = length; // index just after last copy + int dest = length - diff; // number of entries so far not copied + for (int i = indices.length - 1; i >= 0; i--) { + final int index = indices[i]; + if (end - index > 1) { // same as (cp > 0) + final int cp = end - index - 1; + dest -= cp; + System.arraycopy(array, index + 1, result, dest, cp); + // Afer this copy, we still have room for dest items. + } + end = index; + } + if (end > 0) { + System.arraycopy(array, 0, result, 0, end); + } + } + return result; + } + + /** + * Removes multiple array elements specified by indices. + * + * @param array source + * @param indices to remove + * @return new array of same type minus elements specified by the set bits in {@code indices} + * @since 3.2 + */ + // package protected for access by unit tests + static Object removeAll(final Object array, final BitSet indices) { + final int srcLength = ArrayUtils.getLength(array); + // No need to check maxIndex here, because method only currently called from removeElements() + // which guarantee to generate on;y valid bit entries. +// final int maxIndex = indices.length(); +// if (maxIndex > srcLength) { +// throw new IndexOutOfBoundsException("Index: " + (maxIndex-1) + ", Length: " + srcLength); +// } + final int removals = indices.cardinality(); // true bits are items to remove + final Object result = Array.newInstance(array.getClass().getComponentType(), srcLength - removals); + int srcIndex=0; + int destIndex=0; + int count; + int set; + while((set = indices.nextSetBit(srcIndex)) != -1){ + count = set - srcIndex; + if (count > 0) { + System.arraycopy(array, srcIndex, result, destIndex, count); + destIndex += count; + } + srcIndex = indices.nextClearBit(set); + } + count = srcLength - srcIndex; + if (count > 0) { + System.arraycopy(array, srcIndex, result, destIndex, count); + } + return result; + } + + /** + *

              This method checks whether the provided array is sorted according to the class's + * {@code compareTo} method.

              + * + * @param array the array to check + * @param the datatype of the array to check, it must implement {@code Comparable} + * @return whether the array is sorted + * @since 3.4 + */ + public static > boolean isSorted(final T[] array) { + return isSorted(array, new Comparator() { + @Override + public int compare(T o1, T o2) { + return o1.compareTo(o2); + } + }); + } + + + /** + *

              This method checks whether the provided array is sorted according to the provided {@code Comparator}.

              + * + * @param array the array to check + * @param comparator the {@code Comparator} to compare over + * @param the datatype of the array + * @return whether the array is sorted + * @since 3.4 + */ + public static boolean isSorted(final T[] array, final Comparator comparator) { + if (comparator == null) { + throw new IllegalArgumentException("Comparator should not be null."); + } + + if(array == null || array.length < 2) { + return true; + } + + T previous = array[0]; + final int n = array.length; + for(int i = 1; i < n; i++) { + final T current = array[i]; + if (comparator.compare(previous, current) > 0) { + return false; + } + + previous = current; + } + return true; + } + + /** + *

              This method checks whether the provided array is sorted according to natural ordering.

              + * + * @param array the array to check + * @return whether the array is sorted according to natural ordering + * @since 3.4 + */ + public static boolean isSorted(int[] array) { + if(array == null || array.length < 2) { + return true; + } + + int previous = array[0]; + final int n = array.length; + for(int i = 1; i < n; i++) { + final int current = array[i]; + if(NumberUtils.compare(previous, current) > 0) { + return false; + } + + previous = current; + } + return true; + } + + /** + *

              This method checks whether the provided array is sorted according to natural ordering.

              + * + * @param array the array to check + * @return whether the array is sorted according to natural ordering + * @since 3.4 + */ + public static boolean isSorted(long[] array) { + if(array == null || array.length < 2) { + return true; + } + + long previous = array[0]; + final int n = array.length; + for(int i = 1; i < n; i++) { + final long current = array[i]; + if(NumberUtils.compare(previous, current) > 0) { + return false; + } + + previous = current; + } + return true; + } + + /** + *

              This method checks whether the provided array is sorted according to natural ordering.

              + * + * @param array the array to check + * @return whether the array is sorted according to natural ordering + * @since 3.4 + */ + public static boolean isSorted(short[] array) { + if(array == null || array.length < 2) { + return true; + } + + short previous = array[0]; + final int n = array.length; + for(int i = 1; i < n; i++) { + final short current = array[i]; + if(NumberUtils.compare(previous, current) > 0) { + return false; + } + + previous = current; + } + return true; + } + + /** + *

              This method checks whether the provided array is sorted according to natural ordering.

              + * + * @param array the array to check + * @return whether the array is sorted according to natural ordering + * @since 3.4 + */ + public static boolean isSorted(final double[] array) { + if(array == null || array.length < 2) { + return true; + } + + double previous = array[0]; + final int n = array.length; + for(int i = 1; i < n; i++) { + final double current = array[i]; + if(Double.compare(previous, current) > 0) { + return false; + } + + previous = current; + } + return true; + } + + /** + *

              This method checks whether the provided array is sorted according to natural ordering.

              + * + * @param array the array to check + * @return whether the array is sorted according to natural ordering + * @since 3.4 + */ + public static boolean isSorted(final float[] array) { + if(array == null || array.length < 2) { + return true; + } + + float previous = array[0]; + final int n = array.length; + for(int i = 1; i < n; i++) { + final float current = array[i]; + if(Float.compare(previous, current) > 0) { + return false; + } + + previous = current; + } + return true; + } + + /** + *

              This method checks whether the provided array is sorted according to natural ordering.

              + * + * @param array the array to check + * @return whether the array is sorted according to natural ordering + * @since 3.4 + */ + public static boolean isSorted(byte[] array) { + if(array == null || array.length < 2) { + return true; + } + + byte previous = array[0]; + final int n = array.length; + for(int i = 1; i < n; i++) { + final byte current = array[i]; + if(NumberUtils.compare(previous, current) > 0) { + return false; + } + + previous = current; + } + return true; + } + + /** + *

              This method checks whether the provided array is sorted according to natural ordering.

              + * + * @param array the array to check + * @return whether the array is sorted according to natural ordering + * @since 3.4 + */ + public static boolean isSorted(char[] array) { + if(array == null || array.length < 2) { + return true; + } + + char previous = array[0]; + final int n = array.length; + for(int i = 1; i < n; i++) { + final char current = array[i]; + if(CharUtils.compare(previous, current) > 0) { + return false; + } + + previous = current; + } + return true; + } + + /** + *

              This method checks whether the provided array is sorted according to natural ordering + * ({@code false} before {@code true}).

              + * + * @param array the array to check + * @return whether the array is sorted according to natural ordering + * @since 3.4 + */ + public static boolean isSorted(boolean[] array) { + if(array == null || array.length < 2) { + return true; + } + + boolean previous = array[0]; + final int n = array.length; + for(int i = 1; i < n; i++) { + final boolean current = array[i]; + if(BooleanUtils.compare(previous, current) > 0) { + return false; + } + + previous = current; + } + return true; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/BitField.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/BitField.java new file mode 100644 index 000000000..7078cfbe6 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/BitField.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +/** + *

              Supports operations on bit-mapped fields. Instances of this class can be + * used to store a flag or data within an {@code int}, {@code short} or + * {@code byte}.

              + * + *

              Each {@code BitField} is constructed with a mask value, which indicates + * the bits that will be used to store and retrieve the data for that field. + * For instance, the mask {@code 0xFF} indicates the least-significant byte + * should be used to store the data.

              + * + *

              As an example, consider a car painting machine that accepts + * paint instructions as integers. Bit fields can be used to encode this:

              + * + *
              + *    // blue, green and red are 1 byte values (0-255) stored in the three least 
              + *    // significant bytes
              + *    BitField blue = new BitField(0xFF);
              + *    BitField green = new BitField(0xFF00);
              + *    BitField red = new BitField(0xFF0000);
              + * 
              + *    // anyColor is a flag triggered if any color is used
              + *    BitField anyColor = new BitField(0xFFFFFF);
              + * 
              + *    // isMetallic is a single bit flag
              + *    BitField isMetallic = new BitField(0x1000000);
              + *
              + * + *

              Using these {@code BitField} instances, a paint instruction can be + * encoded into an integer:

              + * + *
              + *    int paintInstruction = 0;
              + *    paintInstruction = red.setValue(paintInstruction, 35);
              + *    paintInstruction = green.setValue(paintInstruction, 100);
              + *    paintInstruction = blue.setValue(paintInstruction, 255);
              + *
              + * + *

              Flags and data can be retrieved from the integer:

              + * + *
              + *    // Prints true if red, green or blue is non-zero
              + *    System.out.println(anyColor.isSet(paintInstruction));   // prints true
              + *   
              + *    // Prints value of red, green and blue
              + *    System.out.println(red.getValue(paintInstruction));     // prints 35
              + *    System.out.println(green.getValue(paintInstruction));   // prints 100
              + *    System.out.println(blue.getValue(paintInstruction));    // prints 255
              + *   
              + *    // Prints true if isMetallic was set 
              + *    System.out.println(isMetallic.isSet(paintInstruction)); // prints false
              + *
              + * + * @since 2.0 + * @version $Id: BitField.java 1592457 2014-05-05 07:05:33Z djones $ + */ +public class BitField { + + private final int _mask; + private final int _shift_count; + + /** + *

              Creates a BitField instance.

              + * + * @param mask the mask specifying which bits apply to this + * BitField. Bits that are set in this mask are the bits + * that this BitField operates on + */ + public BitField(final int mask) { + _mask = mask; + int count = 0; + int bit_pattern = mask; + + if (bit_pattern != 0) { + while ((bit_pattern & 1) == 0) { + count++; + bit_pattern >>= 1; + } + } + _shift_count = count; + } + + /** + *

              Obtains the value for the specified BitField, appropriately + * shifted right.

              + * + *

              Many users of a BitField will want to treat the specified + * bits as an int value, and will not want to be aware that the + * value is stored as a BitField (and so shifted left so many + * bits).

              + * + * @see #setValue(int,int) + * @param holder the int data containing the bits we're interested + * in + * @return the selected bits, shifted right appropriately + */ + public int getValue(final int holder) { + return getRawValue(holder) >> _shift_count; + } + + /** + *

              Obtains the value for the specified BitField, appropriately + * shifted right, as a short.

              + * + *

              Many users of a BitField will want to treat the specified + * bits as an int value, and will not want to be aware that the + * value is stored as a BitField (and so shifted left so many + * bits).

              + * + * @see #setShortValue(short,short) + * @param holder the short data containing the bits we're + * interested in + * @return the selected bits, shifted right appropriately + */ + public short getShortValue(final short holder) { + return (short) getValue(holder); + } + + /** + *

              Obtains the value for the specified BitField, unshifted.

              + * + * @param holder the int data containing the bits we're + * interested in + * @return the selected bits + */ + public int getRawValue(final int holder) { + return holder & _mask; + } + + /** + *

              Obtains the value for the specified BitField, unshifted.

              + * + * @param holder the short data containing the bits we're + * interested in + * @return the selected bits + */ + public short getShortRawValue(final short holder) { + return (short) getRawValue(holder); + } + + /** + *

              Returns whether the field is set or not.

              + * + *

              This is most commonly used for a single-bit field, which is + * often used to represent a boolean value; the results of using + * it for a multi-bit field is to determine whether *any* of its + * bits are set.

              + * + * @param holder the int data containing the bits we're interested + * in + * @return {@code true} if any of the bits are set, + * else {@code false} + */ + public boolean isSet(final int holder) { + return (holder & _mask) != 0; + } + + /** + *

              Returns whether all of the bits are set or not.

              + * + *

              This is a stricter test than {@link #isSet(int)}, + * in that all of the bits in a multi-bit set must be set + * for this method to return {@code true}.

              + * + * @param holder the int data containing the bits we're + * interested in + * @return {@code true} if all of the bits are set, + * else {@code false} + */ + public boolean isAllSet(final int holder) { + return (holder & _mask) == _mask; + } + + /** + *

              Replaces the bits with new values.

              + * + * @see #getValue(int) + * @param holder the int data containing the bits we're + * interested in + * @param value the new value for the specified bits + * @return the value of holder with the bits from the value + * parameter replacing the old bits + */ + public int setValue(final int holder, final int value) { + return (holder & ~_mask) | ((value << _shift_count) & _mask); + } + + /** + *

              Replaces the bits with new values.

              + * + * @see #getShortValue(short) + * @param holder the short data containing the bits we're + * interested in + * @param value the new value for the specified bits + * @return the value of holder with the bits from the value + * parameter replacing the old bits + */ + public short setShortValue(final short holder, final short value) { + return (short) setValue(holder, value); + } + + /** + *

              Clears the bits.

              + * + * @param holder the int data containing the bits we're + * interested in + * @return the value of holder with the specified bits cleared + * (set to {@code 0}) + */ + public int clear(final int holder) { + return holder & ~_mask; + } + + /** + *

              Clears the bits.

              + * + * @param holder the short data containing the bits we're + * interested in + * @return the value of holder with the specified bits cleared + * (set to {@code 0}) + */ + public short clearShort(final short holder) { + return (short) clear(holder); + } + + /** + *

              Clears the bits.

              + * + * @param holder the byte data containing the bits we're + * interested in + * + * @return the value of holder with the specified bits cleared + * (set to {@code 0}) + */ + public byte clearByte(final byte holder) { + return (byte) clear(holder); + } + + /** + *

              Sets the bits.

              + * + * @param holder the int data containing the bits we're + * interested in + * @return the value of holder with the specified bits set + * to {@code 1} + */ + public int set(final int holder) { + return holder | _mask; + } + + /** + *

              Sets the bits.

              + * + * @param holder the short data containing the bits we're + * interested in + * @return the value of holder with the specified bits set + * to {@code 1} + */ + public short setShort(final short holder) { + return (short) set(holder); + } + + /** + *

              Sets the bits.

              + * + * @param holder the byte data containing the bits we're + * interested in + * + * @return the value of holder with the specified bits set + * to {@code 1} + */ + public byte setByte(final byte holder) { + return (byte) set(holder); + } + + /** + *

              Sets a boolean BitField.

              + * + * @param holder the int data containing the bits we're + * interested in + * @param flag indicating whether to set or clear the bits + * @return the value of holder with the specified bits set or + * cleared + */ + public int setBoolean(final int holder, final boolean flag) { + return flag ? set(holder) : clear(holder); + } + + /** + *

              Sets a boolean BitField.

              + * + * @param holder the short data containing the bits we're + * interested in + * @param flag indicating whether to set or clear the bits + * @return the value of holder with the specified bits set or + * cleared + */ + public short setShortBoolean(final short holder, final boolean flag) { + return flag ? setShort(holder) : clearShort(holder); + } + + /** + *

              Sets a boolean BitField.

              + * + * @param holder the byte data containing the bits we're + * interested in + * @param flag indicating whether to set or clear the bits + * @return the value of holder with the specified bits set or + * cleared + */ + public byte setByteBoolean(final byte holder, final boolean flag) { + return flag ? setByte(holder) : clearByte(holder); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/BooleanUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/BooleanUtils.java new file mode 100644 index 000000000..fd89c3a60 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/BooleanUtils.java @@ -0,0 +1,1109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import com.fr.third.org.apache.commons.lang3.math.NumberUtils; + +/** + *

              Operations on boolean primitives and Boolean objects.

              + * + *

              This class tries to handle {@code null} input gracefully. + * An exception will not be thrown for a {@code null} input. + * Each method documents its behaviour in more detail.

              + * + *

              #ThreadSafe#

              + * @since 2.0 + * @version $Id: BooleanUtils.java 1632874 2014-10-19 05:52:37Z djones $ + */ +public class BooleanUtils { + + /** + *

              {@code BooleanUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as {@code BooleanUtils.negate(true);}.

              + * + *

              This constructor is public to permit tools that require a JavaBean instance + * to operate.

              + */ + public BooleanUtils() { + super(); + } + + // Boolean utilities + //-------------------------------------------------------------------------- + /** + *

              Negates the specified boolean.

              + * + *

              If {@code null} is passed in, {@code null} will be returned.

              + * + *

              NOTE: This returns null and will throw a NullPointerException if autoboxed to a boolean.

              + * + *
              +     *   BooleanUtils.negate(Boolean.TRUE)  = Boolean.FALSE;
              +     *   BooleanUtils.negate(Boolean.FALSE) = Boolean.TRUE;
              +     *   BooleanUtils.negate(null)          = null;
              +     * 
              + * + * @param bool the Boolean to negate, may be null + * @return the negated Boolean, or {@code null} if {@code null} input + */ + public static Boolean negate(final Boolean bool) { + if (bool == null) { + return null; + } + return bool.booleanValue() ? Boolean.FALSE : Boolean.TRUE; + } + + // boolean Boolean methods + //----------------------------------------------------------------------- + /** + *

              Checks if a {@code Boolean} value is {@code true}, + * handling {@code null} by returning {@code false}.

              + * + *
              +     *   BooleanUtils.isTrue(Boolean.TRUE)  = true
              +     *   BooleanUtils.isTrue(Boolean.FALSE) = false
              +     *   BooleanUtils.isTrue(null)          = false
              +     * 
              + * + * @param bool the boolean to check, null returns {@code false} + * @return {@code true} only if the input is non-null and true + * @since 2.1 + */ + public static boolean isTrue(final Boolean bool) { + return Boolean.TRUE.equals(bool); + } + + /** + *

              Checks if a {@code Boolean} value is not {@code true}, + * handling {@code null} by returning {@code true}.

              + * + *
              +     *   BooleanUtils.isNotTrue(Boolean.TRUE)  = false
              +     *   BooleanUtils.isNotTrue(Boolean.FALSE) = true
              +     *   BooleanUtils.isNotTrue(null)          = true
              +     * 
              + * + * @param bool the boolean to check, null returns {@code true} + * @return {@code true} if the input is null or false + * @since 2.3 + */ + public static boolean isNotTrue(final Boolean bool) { + return !isTrue(bool); + } + + /** + *

              Checks if a {@code Boolean} value is {@code false}, + * handling {@code null} by returning {@code false}.

              + * + *
              +     *   BooleanUtils.isFalse(Boolean.TRUE)  = false
              +     *   BooleanUtils.isFalse(Boolean.FALSE) = true
              +     *   BooleanUtils.isFalse(null)          = false
              +     * 
              + * + * @param bool the boolean to check, null returns {@code false} + * @return {@code true} only if the input is non-null and false + * @since 2.1 + */ + public static boolean isFalse(final Boolean bool) { + return Boolean.FALSE.equals(bool); + } + + /** + *

              Checks if a {@code Boolean} value is not {@code false}, + * handling {@code null} by returning {@code true}.

              + * + *
              +     *   BooleanUtils.isNotFalse(Boolean.TRUE)  = true
              +     *   BooleanUtils.isNotFalse(Boolean.FALSE) = false
              +     *   BooleanUtils.isNotFalse(null)          = true
              +     * 
              + * + * @param bool the boolean to check, null returns {@code true} + * @return {@code true} if the input is null or true + * @since 2.3 + */ + public static boolean isNotFalse(final Boolean bool) { + return !isFalse(bool); + } + + //----------------------------------------------------------------------- + /** + *

              Converts a Boolean to a boolean handling {@code null} + * by returning {@code false}.

              + * + *
              +     *   BooleanUtils.toBoolean(Boolean.TRUE)  = true
              +     *   BooleanUtils.toBoolean(Boolean.FALSE) = false
              +     *   BooleanUtils.toBoolean(null)          = false
              +     * 
              + * + * @param bool the boolean to convert + * @return {@code true} or {@code false}, {@code null} returns {@code false} + */ + public static boolean toBoolean(final Boolean bool) { + return bool != null && bool.booleanValue(); + } + + /** + *

              Converts a Boolean to a boolean handling {@code null}.

              + * + *
              +     *   BooleanUtils.toBooleanDefaultIfNull(Boolean.TRUE, false) = true
              +     *   BooleanUtils.toBooleanDefaultIfNull(Boolean.FALSE, true) = false
              +     *   BooleanUtils.toBooleanDefaultIfNull(null, true)          = true
              +     * 
              + * + * @param bool the boolean to convert + * @param valueIfNull the boolean value to return if {@code null} + * @return {@code true} or {@code false} + */ + public static boolean toBooleanDefaultIfNull(final Boolean bool, final boolean valueIfNull) { + if (bool == null) { + return valueIfNull; + } + return bool.booleanValue(); + } + + // Integer to Boolean methods + //----------------------------------------------------------------------- + /** + *

              Converts an int to a boolean using the convention that {@code zero} + * is {@code false}.

              + * + *
              +     *   BooleanUtils.toBoolean(0) = false
              +     *   BooleanUtils.toBoolean(1) = true
              +     *   BooleanUtils.toBoolean(2) = true
              +     * 
              + * + * @param value the int to convert + * @return {@code true} if non-zero, {@code false} + * if zero + */ + public static boolean toBoolean(final int value) { + return value != 0; + } + + /** + *

              Converts an int to a Boolean using the convention that {@code zero} + * is {@code false}.

              + * + *
              +     *   BooleanUtils.toBoolean(0) = Boolean.FALSE
              +     *   BooleanUtils.toBoolean(1) = Boolean.TRUE
              +     *   BooleanUtils.toBoolean(2) = Boolean.TRUE
              +     * 
              + * + * @param value the int to convert + * @return Boolean.TRUE if non-zero, Boolean.FALSE if zero, + * {@code null} if {@code null} + */ + public static Boolean toBooleanObject(final int value) { + return value == 0 ? Boolean.FALSE : Boolean.TRUE; + } + + /** + *

              Converts an Integer to a Boolean using the convention that {@code zero} + * is {@code false}.

              + * + *

              {@code null} will be converted to {@code null}.

              + * + *

              NOTE: This returns null and will throw a NullPointerException if autoboxed to a boolean.

              + * + *
              +     *   BooleanUtils.toBoolean(Integer.valueOf(0))    = Boolean.FALSE
              +     *   BooleanUtils.toBoolean(Integer.valueOf(1))    = Boolean.TRUE
              +     *   BooleanUtils.toBoolean(Integer.valueOf(null)) = null
              +     * 
              + * + * @param value the Integer to convert + * @return Boolean.TRUE if non-zero, Boolean.FALSE if zero, + * {@code null} if {@code null} input + */ + public static Boolean toBooleanObject(final Integer value) { + if (value == null) { + return null; + } + return value.intValue() == 0 ? Boolean.FALSE : Boolean.TRUE; + } + + /** + *

              Converts an int to a boolean specifying the conversion values.

              + * + *
              +     *   BooleanUtils.toBoolean(0, 1, 0) = false
              +     *   BooleanUtils.toBoolean(1, 1, 0) = true
              +     *   BooleanUtils.toBoolean(2, 1, 2) = false
              +     *   BooleanUtils.toBoolean(2, 2, 0) = true
              +     * 
              + * + * @param value the Integer to convert + * @param trueValue the value to match for {@code true} + * @param falseValue the value to match for {@code false} + * @return {@code true} or {@code false} + * @throws IllegalArgumentException if no match + */ + public static boolean toBoolean(final int value, final int trueValue, final int falseValue) { + if (value == trueValue) { + return true; + } + if (value == falseValue) { + return false; + } + // no match + throw new IllegalArgumentException("The Integer did not match either specified value"); + } + + /** + *

              Converts an Integer to a boolean specifying the conversion values.

              + * + *
              +     *   BooleanUtils.toBoolean(Integer.valueOf(0), Integer.valueOf(1), Integer.valueOf(0)) = false
              +     *   BooleanUtils.toBoolean(Integer.valueOf(1), Integer.valueOf(1), Integer.valueOf(0)) = true
              +     *   BooleanUtils.toBoolean(Integer.valueOf(2), Integer.valueOf(1), Integer.valueOf(2)) = false
              +     *   BooleanUtils.toBoolean(Integer.valueOf(2), Integer.valueOf(2), Integer.valueOf(0)) = true
              +     *   BooleanUtils.toBoolean(null, null, Integer.valueOf(0))                     = true
              +     * 
              + * + * @param value the Integer to convert + * @param trueValue the value to match for {@code true}, may be {@code null} + * @param falseValue the value to match for {@code false}, may be {@code null} + * @return {@code true} or {@code false} + * @throws IllegalArgumentException if no match + */ + public static boolean toBoolean(final Integer value, final Integer trueValue, final Integer falseValue) { + if (value == null) { + if (trueValue == null) { + return true; + } + if (falseValue == null) { + return false; + } + } else if (value.equals(trueValue)) { + return true; + } else if (value.equals(falseValue)) { + return false; + } + // no match + throw new IllegalArgumentException("The Integer did not match either specified value"); + } + + /** + *

              Converts an int to a Boolean specifying the conversion values.

              + * + *

              NOTE: This returns null and will throw a NullPointerException if autoboxed to a boolean.

              + * + *
              +     *   BooleanUtils.toBooleanObject(0, 0, 2, 3) = Boolean.TRUE
              +     *   BooleanUtils.toBooleanObject(2, 1, 2, 3) = Boolean.FALSE
              +     *   BooleanUtils.toBooleanObject(3, 1, 2, 3) = null
              +     * 
              + * + * @param value the Integer to convert + * @param trueValue the value to match for {@code true} + * @param falseValue the value to match for {@code false} + * @param nullValue the value to to match for {@code null} + * @return Boolean.TRUE, Boolean.FALSE, or {@code null} + * @throws IllegalArgumentException if no match + */ + public static Boolean toBooleanObject(final int value, final int trueValue, final int falseValue, final int nullValue) { + if (value == trueValue) { + return Boolean.TRUE; + } + if (value == falseValue) { + return Boolean.FALSE; + } + if (value == nullValue) { + return null; + } + // no match + throw new IllegalArgumentException("The Integer did not match any specified value"); + } + + /** + *

              Converts an Integer to a Boolean specifying the conversion values.

              + * + *

              NOTE: This returns null and will throw a NullPointerException if autoboxed to a boolean.

              + * + *
              +     *   BooleanUtils.toBooleanObject(Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(2), Integer.valueOf(3)) = Boolean.TRUE
              +     *   BooleanUtils.toBooleanObject(Integer.valueOf(2), Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)) = Boolean.FALSE
              +     *   BooleanUtils.toBooleanObject(Integer.valueOf(3), Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)) = null
              +     * 
              + * + * @param value the Integer to convert + * @param trueValue the value to match for {@code true}, may be {@code null} + * @param falseValue the value to match for {@code false}, may be {@code null} + * @param nullValue the value to to match for {@code null}, may be {@code null} + * @return Boolean.TRUE, Boolean.FALSE, or {@code null} + * @throws IllegalArgumentException if no match + */ + public static Boolean toBooleanObject(final Integer value, final Integer trueValue, final Integer falseValue, final Integer nullValue) { + if (value == null) { + if (trueValue == null) { + return Boolean.TRUE; + } + if (falseValue == null) { + return Boolean.FALSE; + } + if (nullValue == null) { + return null; + } + } else if (value.equals(trueValue)) { + return Boolean.TRUE; + } else if (value.equals(falseValue)) { + return Boolean.FALSE; + } else if (value.equals(nullValue)) { + return null; + } + // no match + throw new IllegalArgumentException("The Integer did not match any specified value"); + } + + // Boolean to Integer methods + //----------------------------------------------------------------------- + /** + *

              Converts a boolean to an int using the convention that + * {@code zero} is {@code false}.

              + * + *
              +     *   BooleanUtils.toInteger(true)  = 1
              +     *   BooleanUtils.toInteger(false) = 0
              +     * 
              + * + * @param bool the boolean to convert + * @return one if {@code true}, zero if {@code false} + */ + public static int toInteger(final boolean bool) { + return bool ? 1 : 0; + } + + /** + *

              Converts a boolean to an Integer using the convention that + * {@code zero} is {@code false}.

              + * + *
              +     *   BooleanUtils.toIntegerObject(true)  = Integer.valueOf(1)
              +     *   BooleanUtils.toIntegerObject(false) = Integer.valueOf(0)
              +     * 
              + * + * @param bool the boolean to convert + * @return one if {@code true}, zero if {@code false} + */ + public static Integer toIntegerObject(final boolean bool) { + return bool ? NumberUtils.INTEGER_ONE : NumberUtils.INTEGER_ZERO; + } + + /** + *

              Converts a Boolean to a Integer using the convention that + * {@code zero} is {@code false}.

              + * + *

              {@code null} will be converted to {@code null}.

              + * + *
              +     *   BooleanUtils.toIntegerObject(Boolean.TRUE)  = Integer.valueOf(1)
              +     *   BooleanUtils.toIntegerObject(Boolean.FALSE) = Integer.valueOf(0)
              +     * 
              + * + * @param bool the Boolean to convert + * @return one if Boolean.TRUE, zero if Boolean.FALSE, {@code null} if {@code null} + */ + public static Integer toIntegerObject(final Boolean bool) { + if (bool == null) { + return null; + } + return bool.booleanValue() ? NumberUtils.INTEGER_ONE : NumberUtils.INTEGER_ZERO; + } + + /** + *

              Converts a boolean to an int specifying the conversion values.

              + * + *
              +     *   BooleanUtils.toInteger(true, 1, 0)  = 1
              +     *   BooleanUtils.toInteger(false, 1, 0) = 0
              +     * 
              + * + * @param bool the to convert + * @param trueValue the value to return if {@code true} + * @param falseValue the value to return if {@code false} + * @return the appropriate value + */ + public static int toInteger(final boolean bool, final int trueValue, final int falseValue) { + return bool ? trueValue : falseValue; + } + + /** + *

              Converts a Boolean to an int specifying the conversion values.

              + * + *
              +     *   BooleanUtils.toInteger(Boolean.TRUE, 1, 0, 2)  = 1
              +     *   BooleanUtils.toInteger(Boolean.FALSE, 1, 0, 2) = 0
              +     *   BooleanUtils.toInteger(null, 1, 0, 2)          = 2
              +     * 
              + * + * @param bool the Boolean to convert + * @param trueValue the value to return if {@code true} + * @param falseValue the value to return if {@code false} + * @param nullValue the value to return if {@code null} + * @return the appropriate value + */ + public static int toInteger(final Boolean bool, final int trueValue, final int falseValue, final int nullValue) { + if (bool == null) { + return nullValue; + } + return bool.booleanValue() ? trueValue : falseValue; + } + + /** + *

              Converts a boolean to an Integer specifying the conversion values.

              + * + *
              +     *   BooleanUtils.toIntegerObject(true, Integer.valueOf(1), Integer.valueOf(0))  = Integer.valueOf(1)
              +     *   BooleanUtils.toIntegerObject(false, Integer.valueOf(1), Integer.valueOf(0)) = Integer.valueOf(0)
              +     * 
              + * + * @param bool the to convert + * @param trueValue the value to return if {@code true}, may be {@code null} + * @param falseValue the value to return if {@code false}, may be {@code null} + * @return the appropriate value + */ + public static Integer toIntegerObject(final boolean bool, final Integer trueValue, final Integer falseValue) { + return bool ? trueValue : falseValue; + } + + /** + *

              Converts a Boolean to an Integer specifying the conversion values.

              + * + *
              +     *   BooleanUtils.toIntegerObject(Boolean.TRUE, Integer.valueOf(1), Integer.valueOf(0), Integer.valueOf(2))  = Integer.valueOf(1)
              +     *   BooleanUtils.toIntegerObject(Boolean.FALSE, Integer.valueOf(1), Integer.valueOf(0), Integer.valueOf(2)) = Integer.valueOf(0)
              +     *   BooleanUtils.toIntegerObject(null, Integer.valueOf(1), Integer.valueOf(0), Integer.valueOf(2))          = Integer.valueOf(2)
              +     * 
              + * + * @param bool the Boolean to convert + * @param trueValue the value to return if {@code true}, may be {@code null} + * @param falseValue the value to return if {@code false}, may be {@code null} + * @param nullValue the value to return if {@code null}, may be {@code null} + * @return the appropriate value + */ + public static Integer toIntegerObject(final Boolean bool, final Integer trueValue, final Integer falseValue, final Integer nullValue) { + if (bool == null) { + return nullValue; + } + return bool.booleanValue() ? trueValue : falseValue; + } + + // String to Boolean methods + //----------------------------------------------------------------------- + /** + *

              Converts a String to a Boolean.

              + * + *

              {@code 'true'}, {@code 'on'}, {@code 'y'}, {@code 't'} or {@code 'yes'} + * (case insensitive) will return {@code true}. + * {@code 'false'}, {@code 'off'}, {@code 'n'}, {@code 'f'} or {@code 'no'} + * (case insensitive) will return {@code false}. + * Otherwise, {@code null} is returned.

              + * + *

              NOTE: This returns null and will throw a NullPointerException if autoboxed to a boolean.

              + * + *
              +     *   // N.B. case is not significant
              +     *   BooleanUtils.toBooleanObject(null)    = null
              +     *   BooleanUtils.toBooleanObject("true")  = Boolean.TRUE
              +     *   BooleanUtils.toBooleanObject("T")     = Boolean.TRUE // i.e. T[RUE]
              +     *   BooleanUtils.toBooleanObject("false") = Boolean.FALSE
              +     *   BooleanUtils.toBooleanObject("f")     = Boolean.FALSE // i.e. f[alse]
              +     *   BooleanUtils.toBooleanObject("No")    = Boolean.FALSE
              +     *   BooleanUtils.toBooleanObject("n")     = Boolean.FALSE // i.e. n[o]
              +     *   BooleanUtils.toBooleanObject("on")    = Boolean.TRUE
              +     *   BooleanUtils.toBooleanObject("ON")    = Boolean.TRUE
              +     *   BooleanUtils.toBooleanObject("off")   = Boolean.FALSE
              +     *   BooleanUtils.toBooleanObject("oFf")   = Boolean.FALSE
              +     *   BooleanUtils.toBooleanObject("yes")   = Boolean.TRUE
              +     *   BooleanUtils.toBooleanObject("Y")     = Boolean.TRUE // i.e. Y[ES]
              +     *   BooleanUtils.toBooleanObject("blue")  = null
              +     *   BooleanUtils.toBooleanObject("true ") = null // trailing space (too long)
              +     *   BooleanUtils.toBooleanObject("ono")   = null // does not match on or no
              +     * 
              + * + * @param str the String to check; upper and lower case are treated as the same + * @return the Boolean value of the string, {@code null} if no match or {@code null} input + */ + public static Boolean toBooleanObject(final String str) { + // Previously used equalsIgnoreCase, which was fast for interned 'true'. + // Non interned 'true' matched 15 times slower. + // + // Optimisation provides same performance as before for interned 'true'. + // Similar performance for null, 'false', and other strings not length 2/3/4. + // 'true'/'TRUE' match 4 times slower, 'tRUE'/'True' 7 times slower. + if (str == "true") { + return Boolean.TRUE; + } + if (str == null) { + return null; + } + switch (str.length()) { + case 1: { + final char ch0 = str.charAt(0); + if (ch0 == 'y' || ch0 == 'Y' || + ch0 == 't' || ch0 == 'T') { + return Boolean.TRUE; + } + if (ch0 == 'n' || ch0 == 'N' || + ch0 == 'f' || ch0 == 'F') { + return Boolean.FALSE; + } + break; + } + case 2: { + final char ch0 = str.charAt(0); + final char ch1 = str.charAt(1); + if ((ch0 == 'o' || ch0 == 'O') && + (ch1 == 'n' || ch1 == 'N') ) { + return Boolean.TRUE; + } + if ((ch0 == 'n' || ch0 == 'N') && + (ch1 == 'o' || ch1 == 'O') ) { + return Boolean.FALSE; + } + break; + } + case 3: { + final char ch0 = str.charAt(0); + final char ch1 = str.charAt(1); + final char ch2 = str.charAt(2); + if ((ch0 == 'y' || ch0 == 'Y') && + (ch1 == 'e' || ch1 == 'E') && + (ch2 == 's' || ch2 == 'S') ) { + return Boolean.TRUE; + } + if ((ch0 == 'o' || ch0 == 'O') && + (ch1 == 'f' || ch1 == 'F') && + (ch2 == 'f' || ch2 == 'F') ) { + return Boolean.FALSE; + } + break; + } + case 4: { + final char ch0 = str.charAt(0); + final char ch1 = str.charAt(1); + final char ch2 = str.charAt(2); + final char ch3 = str.charAt(3); + if ((ch0 == 't' || ch0 == 'T') && + (ch1 == 'r' || ch1 == 'R') && + (ch2 == 'u' || ch2 == 'U') && + (ch3 == 'e' || ch3 == 'E') ) { + return Boolean.TRUE; + } + break; + } + case 5: { + final char ch0 = str.charAt(0); + final char ch1 = str.charAt(1); + final char ch2 = str.charAt(2); + final char ch3 = str.charAt(3); + final char ch4 = str.charAt(4); + if ((ch0 == 'f' || ch0 == 'F') && + (ch1 == 'a' || ch1 == 'A') && + (ch2 == 'l' || ch2 == 'L') && + (ch3 == 's' || ch3 == 'S') && + (ch4 == 'e' || ch4 == 'E') ) { + return Boolean.FALSE; + } + break; + } + default: + break; + } + + return null; + } + + /** + *

              Converts a String to a Boolean throwing an exception if no match.

              + * + *

              NOTE: This returns null and will throw a NullPointerException if autoboxed to a boolean.

              + * + *
              +     *   BooleanUtils.toBooleanObject("true", "true", "false", "null")  = Boolean.TRUE
              +     *   BooleanUtils.toBooleanObject("false", "true", "false", "null") = Boolean.FALSE
              +     *   BooleanUtils.toBooleanObject("null", "true", "false", "null")  = null
              +     * 
              + * + * @param str the String to check + * @param trueString the String to match for {@code true} (case sensitive), may be {@code null} + * @param falseString the String to match for {@code false} (case sensitive), may be {@code null} + * @param nullString the String to match for {@code null} (case sensitive), may be {@code null} + * @return the Boolean value of the string, {@code null} if either the String matches {@code nullString} + * or if {@code null} input and {@code nullString} is {@code null} + * @throws IllegalArgumentException if the String doesn't match + */ + public static Boolean toBooleanObject(final String str, final String trueString, final String falseString, final String nullString) { + if (str == null) { + if (trueString == null) { + return Boolean.TRUE; + } + if (falseString == null) { + return Boolean.FALSE; + } + if (nullString == null) { + return null; + } + } else if (str.equals(trueString)) { + return Boolean.TRUE; + } else if (str.equals(falseString)) { + return Boolean.FALSE; + } else if (str.equals(nullString)) { + return null; + } + // no match + throw new IllegalArgumentException("The String did not match any specified value"); + } + + // String to boolean methods + //----------------------------------------------------------------------- + /** + *

              Converts a String to a boolean (optimised for performance).

              + * + *

              {@code 'true'}, {@code 'on'}, {@code 'y'}, {@code 't'} or {@code 'yes'} + * (case insensitive) will return {@code true}. Otherwise, + * {@code false} is returned.

              + * + *

              This method performs 4 times faster (JDK1.4) than + * {@code Boolean.valueOf(String)}. However, this method accepts + * 'on' and 'yes', 't', 'y' as true values. + * + *

              +     *   BooleanUtils.toBoolean(null)    = false
              +     *   BooleanUtils.toBoolean("true")  = true
              +     *   BooleanUtils.toBoolean("TRUE")  = true
              +     *   BooleanUtils.toBoolean("tRUe")  = true
              +     *   BooleanUtils.toBoolean("on")    = true
              +     *   BooleanUtils.toBoolean("yes")   = true
              +     *   BooleanUtils.toBoolean("false") = false
              +     *   BooleanUtils.toBoolean("x gti") = false
              +     *   BooleanUtils.toBooleanObject("y") = true
              +     *   BooleanUtils.toBooleanObject("n") = false
              +     *   BooleanUtils.toBooleanObject("t") = true
              +     *   BooleanUtils.toBooleanObject("f") = false 
              +     * 
              + * + * @param str the String to check + * @return the boolean value of the string, {@code false} if no match or the String is null + */ + public static boolean toBoolean(final String str) { + return toBooleanObject(str) == Boolean.TRUE; + } + + /** + *

              Converts a String to a Boolean throwing an exception if no match found.

              + * + *
              +     *   BooleanUtils.toBoolean("true", "true", "false")  = true
              +     *   BooleanUtils.toBoolean("false", "true", "false") = false
              +     * 
              + * + * @param str the String to check + * @param trueString the String to match for {@code true} (case sensitive), may be {@code null} + * @param falseString the String to match for {@code false} (case sensitive), may be {@code null} + * @return the boolean value of the string + * @throws IllegalArgumentException if the String doesn't match + */ + public static boolean toBoolean(final String str, final String trueString, final String falseString) { + if (str == trueString) { + return true; + } else if (str == falseString) { + return false; + } else if (str != null) { + if (str.equals(trueString)) { + return true; + } else if (str.equals(falseString)) { + return false; + } + } + // no match + throw new IllegalArgumentException("The String did not match either specified value"); + } + + // Boolean to String methods + //----------------------------------------------------------------------- + /** + *

              Converts a Boolean to a String returning {@code 'true'}, + * {@code 'false'}, or {@code null}.

              + * + *
              +     *   BooleanUtils.toStringTrueFalse(Boolean.TRUE)  = "true"
              +     *   BooleanUtils.toStringTrueFalse(Boolean.FALSE) = "false"
              +     *   BooleanUtils.toStringTrueFalse(null)          = null;
              +     * 
              + * + * @param bool the Boolean to check + * @return {@code 'true'}, {@code 'false'}, or {@code null} + */ + public static String toStringTrueFalse(final Boolean bool) { + return toString(bool, "true", "false", null); + } + + /** + *

              Converts a Boolean to a String returning {@code 'on'}, + * {@code 'off'}, or {@code null}.

              + * + *
              +     *   BooleanUtils.toStringOnOff(Boolean.TRUE)  = "on"
              +     *   BooleanUtils.toStringOnOff(Boolean.FALSE) = "off"
              +     *   BooleanUtils.toStringOnOff(null)          = null;
              +     * 
              + * + * @param bool the Boolean to check + * @return {@code 'on'}, {@code 'off'}, or {@code null} + */ + public static String toStringOnOff(final Boolean bool) { + return toString(bool, "on", "off", null); + } + + /** + *

              Converts a Boolean to a String returning {@code 'yes'}, + * {@code 'no'}, or {@code null}.

              + * + *
              +     *   BooleanUtils.toStringYesNo(Boolean.TRUE)  = "yes"
              +     *   BooleanUtils.toStringYesNo(Boolean.FALSE) = "no"
              +     *   BooleanUtils.toStringYesNo(null)          = null;
              +     * 
              + * + * @param bool the Boolean to check + * @return {@code 'yes'}, {@code 'no'}, or {@code null} + */ + public static String toStringYesNo(final Boolean bool) { + return toString(bool, "yes", "no", null); + } + + /** + *

              Converts a Boolean to a String returning one of the input Strings.

              + * + *
              +     *   BooleanUtils.toString(Boolean.TRUE, "true", "false", null)   = "true"
              +     *   BooleanUtils.toString(Boolean.FALSE, "true", "false", null)  = "false"
              +     *   BooleanUtils.toString(null, "true", "false", null)           = null;
              +     * 
              + * + * @param bool the Boolean to check + * @param trueString the String to return if {@code true}, may be {@code null} + * @param falseString the String to return if {@code false}, may be {@code null} + * @param nullString the String to return if {@code null}, may be {@code null} + * @return one of the three input Strings + */ + public static String toString(final Boolean bool, final String trueString, final String falseString, final String nullString) { + if (bool == null) { + return nullString; + } + return bool.booleanValue() ? trueString : falseString; + } + + // boolean to String methods + //----------------------------------------------------------------------- + /** + *

              Converts a boolean to a String returning {@code 'true'} + * or {@code 'false'}.

              + * + *
              +     *   BooleanUtils.toStringTrueFalse(true)   = "true"
              +     *   BooleanUtils.toStringTrueFalse(false)  = "false"
              +     * 
              + * + * @param bool the Boolean to check + * @return {@code 'true'}, {@code 'false'}, or {@code null} + */ + public static String toStringTrueFalse(final boolean bool) { + return toString(bool, "true", "false"); + } + + /** + *

              Converts a boolean to a String returning {@code 'on'} + * or {@code 'off'}.

              + * + *
              +     *   BooleanUtils.toStringOnOff(true)   = "on"
              +     *   BooleanUtils.toStringOnOff(false)  = "off"
              +     * 
              + * + * @param bool the Boolean to check + * @return {@code 'on'}, {@code 'off'}, or {@code null} + */ + public static String toStringOnOff(final boolean bool) { + return toString(bool, "on", "off"); + } + + /** + *

              Converts a boolean to a String returning {@code 'yes'} + * or {@code 'no'}.

              + * + *
              +     *   BooleanUtils.toStringYesNo(true)   = "yes"
              +     *   BooleanUtils.toStringYesNo(false)  = "no"
              +     * 
              + * + * @param bool the Boolean to check + * @return {@code 'yes'}, {@code 'no'}, or {@code null} + */ + public static String toStringYesNo(final boolean bool) { + return toString(bool, "yes", "no"); + } + + /** + *

              Converts a boolean to a String returning one of the input Strings.

              + * + *
              +     *   BooleanUtils.toString(true, "true", "false")   = "true"
              +     *   BooleanUtils.toString(false, "true", "false")  = "false"
              +     * 
              + * + * @param bool the Boolean to check + * @param trueString the String to return if {@code true}, may be {@code null} + * @param falseString the String to return if {@code false}, may be {@code null} + * @return one of the two input Strings + */ + public static String toString(final boolean bool, final String trueString, final String falseString) { + return bool ? trueString : falseString; + } + + // logical operations + // ---------------------------------------------------------------------- + /** + *

              Performs an and on a set of booleans.

              + * + *
              +     *   BooleanUtils.and(true, true)         = true
              +     *   BooleanUtils.and(false, false)       = false
              +     *   BooleanUtils.and(true, false)        = false
              +     *   BooleanUtils.and(true, true, false)  = false
              +     *   BooleanUtils.and(true, true, true)   = true
              +     * 
              + * + * @param array an array of {@code boolean}s + * @return {@code true} if the and is successful. + * @throws IllegalArgumentException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty. + * @since 3.0.1 + */ + public static boolean and(final boolean... array) { + // Validates input + if (array == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + if (array.length == 0) { + throw new IllegalArgumentException("Array is empty"); + } + for (final boolean element : array) { + if (!element) { + return false; + } + } + return true; + } + + /** + *

              Performs an and on an array of Booleans.

              + * + *
              +     *   BooleanUtils.and(Boolean.TRUE, Boolean.TRUE)                 = Boolean.TRUE
              +     *   BooleanUtils.and(Boolean.FALSE, Boolean.FALSE)               = Boolean.FALSE
              +     *   BooleanUtils.and(Boolean.TRUE, Boolean.FALSE)                = Boolean.FALSE
              +     *   BooleanUtils.and(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE)   = Boolean.TRUE
              +     *   BooleanUtils.and(Boolean.FALSE, Boolean.FALSE, Boolean.TRUE) = Boolean.FALSE
              +     *   BooleanUtils.and(Boolean.TRUE, Boolean.FALSE, Boolean.TRUE)  = Boolean.FALSE
              +     * 
              + * + * @param array an array of {@code Boolean}s + * @return {@code true} if the and is successful. + * @throws IllegalArgumentException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty. + * @throws IllegalArgumentException if {@code array} contains a {@code null} + * @since 3.0.1 + */ + public static Boolean and(final Boolean... array) { + if (array == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + if (array.length == 0) { + throw new IllegalArgumentException("Array is empty"); + } + try { + final boolean[] primitive = ArrayUtils.toPrimitive(array); + return and(primitive) ? Boolean.TRUE : Boolean.FALSE; + } catch (final NullPointerException ex) { + throw new IllegalArgumentException("The array must not contain any null elements"); + } + } + + /** + *

              Performs an or on a set of booleans.

              + * + *
              +     *   BooleanUtils.or(true, true)          = true
              +     *   BooleanUtils.or(false, false)        = false
              +     *   BooleanUtils.or(true, false)         = true
              +     *   BooleanUtils.or(true, true, false)   = true
              +     *   BooleanUtils.or(true, true, true)    = true
              +     *   BooleanUtils.or(false, false, false) = false
              +     * 
              + * + * @param array an array of {@code boolean}s + * @return {@code true} if the or is successful. + * @throws IllegalArgumentException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty. + * @since 3.0.1 + */ + public static boolean or(final boolean... array) { + // Validates input + if (array == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + if (array.length == 0) { + throw new IllegalArgumentException("Array is empty"); + } + for (final boolean element : array) { + if (element) { + return true; + } + } + return false; + } + + /** + *

              Performs an or on an array of Booleans.

              + * + *
              +     *   BooleanUtils.or(Boolean.TRUE, Boolean.TRUE)                  = Boolean.TRUE
              +     *   BooleanUtils.or(Boolean.FALSE, Boolean.FALSE)                = Boolean.FALSE
              +     *   BooleanUtils.or(Boolean.TRUE, Boolean.FALSE)                 = Boolean.TRUE
              +     *   BooleanUtils.or(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE)    = Boolean.TRUE
              +     *   BooleanUtils.or(Boolean.FALSE, Boolean.FALSE, Boolean.TRUE)  = Boolean.TRUE
              +     *   BooleanUtils.or(Boolean.TRUE, Boolean.FALSE, Boolean.TRUE)   = Boolean.TRUE
              +     *   BooleanUtils.or(Boolean.FALSE, Boolean.FALSE, Boolean.FALSE) = Boolean.FALSE
              +     * 
              + * + * @param array an array of {@code Boolean}s + * @return {@code true} if the or is successful. + * @throws IllegalArgumentException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty. + * @throws IllegalArgumentException if {@code array} contains a {@code null} + * @since 3.0.1 + */ + public static Boolean or(final Boolean... array) { + if (array == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + if (array.length == 0) { + throw new IllegalArgumentException("Array is empty"); + } + try { + final boolean[] primitive = ArrayUtils.toPrimitive(array); + return or(primitive) ? Boolean.TRUE : Boolean.FALSE; + } catch (final NullPointerException ex) { + throw new IllegalArgumentException("The array must not contain any null elements"); + } + } + + /** + *

              Performs an xor on a set of booleans.

              + * + *
              +     *   BooleanUtils.xor(true, true)   = false
              +     *   BooleanUtils.xor(false, false) = false
              +     *   BooleanUtils.xor(true, false)  = true
              +     *   BooleanUtils.xor(true, true)   = false
              +     *   BooleanUtils.xor(false, false) = false
              +     *   BooleanUtils.xor(true, false)  = true
              +     * 
              + * + * @param array an array of {@code boolean}s + * @return {@code true} if the xor is successful. + * @throws IllegalArgumentException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty. + */ + public static boolean xor(final boolean... array) { + // Validates input + if (array == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + if (array.length == 0) { + throw new IllegalArgumentException("Array is empty"); + } + + // false if the neutral element of the xor operator + boolean result = false; + for (final boolean element : array) { + result ^= element; + } + + return result; + } + + /** + *

              Performs an xor on an array of Booleans.

              + * + *
              +     *   BooleanUtils.xor(new Boolean[] { Boolean.TRUE, Boolean.TRUE })   = Boolean.FALSE
              +     *   BooleanUtils.xor(new Boolean[] { Boolean.FALSE, Boolean.FALSE }) = Boolean.FALSE
              +     *   BooleanUtils.xor(new Boolean[] { Boolean.TRUE, Boolean.FALSE })  = Boolean.TRUE
              +     * 
              + * + * @param array an array of {@code Boolean}s + * @return {@code true} if the xor is successful. + * @throws IllegalArgumentException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty. + * @throws IllegalArgumentException if {@code array} contains a {@code null} + */ + public static Boolean xor(final Boolean... array) { + if (array == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + if (array.length == 0) { + throw new IllegalArgumentException("Array is empty"); + } + try { + final boolean[] primitive = ArrayUtils.toPrimitive(array); + return xor(primitive) ? Boolean.TRUE : Boolean.FALSE; + } catch (final NullPointerException ex) { + throw new IllegalArgumentException("The array must not contain any null elements"); + } + } + + /** + *

              Compares two {@code boolean} values. This is the same functionality as provided in Java 7.

              + * + * @param x the first {@code boolean} to compare + * @param y the second {@code boolean} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code !x && y}; and + * a value greater than {@code 0} if {@code x && !y} + * @since 3.4 + */ + public static int compare(boolean x, boolean y) { + if (x == y) { + return 0; + } + if (x) { + return 1; + } else { + return -1; + } + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharEncoding.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharEncoding.java new file mode 100644 index 000000000..70605bb56 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharEncoding.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.lang3; + +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; + +/** + *

              Character encoding names required of every implementation of the Java platform.

              + * + *

              According to JRE character + * encoding names:

              + * + *

              Every implementation of the Java platform is required to support the following character encodings. + * Consult the release documentation for your implementation to see if any other encodings are supported. + *

              + * + * @see JRE character encoding names + * @since 2.1 + * @version $Id: CharEncoding.java 1459653 2013-03-22 07:37:03Z bayard $ + */ +public class CharEncoding { + + /** + *

              ISO Latin Alphabet #1, also known as ISO-LATIN-1.

              + * + *

              Every implementation of the Java platform is required to support this character encoding.

              + */ + public static final String ISO_8859_1 = "ISO-8859-1"; + + /** + *

              Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block + * of the Unicode character set.

              + * + *

              Every implementation of the Java platform is required to support this character encoding.

              + */ + public static final String US_ASCII = "US-ASCII"; + + /** + *

              Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial + * byte-order mark (either order accepted on input, big-endian used on output).

              + * + *

              Every implementation of the Java platform is required to support this character encoding.

              + */ + public static final String UTF_16 = "UTF-16"; + + /** + *

              Sixteen-bit Unicode Transformation Format, big-endian byte order.

              + * + *

              Every implementation of the Java platform is required to support this character encoding.

              + */ + public static final String UTF_16BE = "UTF-16BE"; + + /** + *

              Sixteen-bit Unicode Transformation Format, little-endian byte order.

              + * + *

              Every implementation of the Java platform is required to support this character encoding.

              + */ + public static final String UTF_16LE = "UTF-16LE"; + + /** + *

              Eight-bit Unicode Transformation Format.

              + * + *

              Every implementation of the Java platform is required to support this character encoding.

              + */ + public static final String UTF_8 = "UTF-8"; + + //----------------------------------------------------------------------- + /** + *

              Returns whether the named charset is supported.

              + * + *

              This is similar to + * java.nio.charset.Charset.isSupported(String) but handles more formats

              + * + * @param name the name of the requested charset; may be either a canonical name or an alias, null returns false + * @return {@code true} if the charset is available in the current Java virtual machine + */ + public static boolean isSupported(final String name) { + if (name == null) { + return false; + } + try { + return Charset.isSupported(name); + } catch (final IllegalCharsetNameException ex) { + return false; + } + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharRange.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharRange.java new file mode 100644 index 000000000..6dd281393 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharRange.java @@ -0,0 +1,360 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + *

              A contiguous range of characters, optionally negated.

              + * + *

              Instances are immutable.

              + * + *

              #ThreadSafe#

              + * @since 1.0 + * @version $Id: CharRange.java 1436770 2013-01-22 07:09:45Z ggregory $ + */ +// TODO: This is no longer public and will be removed later as CharSet is moved +// to depend on Range. +final class CharRange implements Iterable, Serializable { + + /** + * Required for serialization support. Lang version 2.0. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 8270183163158333422L; + + /** The first character, inclusive, in the range. */ + private final char start; + /** The last character, inclusive, in the range. */ + private final char end; + /** True if the range is everything except the characters specified. */ + private final boolean negated; + + /** Cached toString. */ + private transient String iToString; + + /** + *

              Constructs a {@code CharRange} over a set of characters, + * optionally negating the range.

              + * + *

              A negated range includes everything except that defined by the + * start and end characters.

              + * + *

              If start and end are in the wrong order, they are reversed. + * Thus {@code a-e} is the same as {@code e-a}.

              + * + * @param start first character, inclusive, in this range + * @param end last character, inclusive, in this range + * @param negated true to express everything except the range + */ + private CharRange(char start, char end, final boolean negated) { + super(); + if (start > end) { + final char temp = start; + start = end; + end = temp; + } + + this.start = start; + this.end = end; + this.negated = negated; + } + + /** + *

              Constructs a {@code CharRange} over a single character.

              + * + * @param ch only character in this range + * @return the new CharRange object + * @see CharRange#CharRange(char, char, boolean) + * @since 2.5 + */ + public static CharRange is(final char ch) { + return new CharRange(ch, ch, false); + } + + /** + *

              Constructs a negated {@code CharRange} over a single character.

              + * + * @param ch only character in this range + * @return the new CharRange object + * @see CharRange#CharRange(char, char, boolean) + * @since 2.5 + */ + public static CharRange isNot(final char ch) { + return new CharRange(ch, ch, true); + } + + /** + *

              Constructs a {@code CharRange} over a set of characters.

              + * + * @param start first character, inclusive, in this range + * @param end last character, inclusive, in this range + * @return the new CharRange object + * @see CharRange#CharRange(char, char, boolean) + * @since 2.5 + */ + public static CharRange isIn(final char start, final char end) { + return new CharRange(start, end, false); + } + + /** + *

              Constructs a negated {@code CharRange} over a set of characters.

              + * + * @param start first character, inclusive, in this range + * @param end last character, inclusive, in this range + * @return the new CharRange object + * @see CharRange#CharRange(char, char, boolean) + * @since 2.5 + */ + public static CharRange isNotIn(final char start, final char end) { + return new CharRange(start, end, true); + } + + // Accessors + //----------------------------------------------------------------------- + /** + *

              Gets the start character for this character range.

              + * + * @return the start char (inclusive) + */ + public char getStart() { + return this.start; + } + + /** + *

              Gets the end character for this character range.

              + * + * @return the end char (inclusive) + */ + public char getEnd() { + return this.end; + } + + /** + *

              Is this {@code CharRange} negated.

              + * + *

              A negated range includes everything except that defined by the + * start and end characters.

              + * + * @return {@code true} if negated + */ + public boolean isNegated() { + return negated; + } + + // Contains + //----------------------------------------------------------------------- + /** + *

              Is the character specified contained in this range.

              + * + * @param ch the character to check + * @return {@code true} if this range contains the input character + */ + public boolean contains(final char ch) { + return (ch >= start && ch <= end) != negated; + } + + /** + *

              Are all the characters of the passed in range contained in + * this range.

              + * + * @param range the range to check against + * @return {@code true} if this range entirely contains the input range + * @throws IllegalArgumentException if {@code null} input + */ + public boolean contains(final CharRange range) { + if (range == null) { + throw new IllegalArgumentException("The Range must not be null"); + } + if (negated) { + if (range.negated) { + return start >= range.start && end <= range.end; + } + return range.end < start || range.start > end; + } + if (range.negated) { + return start == 0 && end == Character.MAX_VALUE; + } + return start <= range.start && end >= range.end; + } + + // Basics + //----------------------------------------------------------------------- + /** + *

              Compares two CharRange objects, returning true if they represent + * exactly the same range of characters defined in the same way.

              + * + * @param obj the object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof CharRange == false) { + return false; + } + final CharRange other = (CharRange) obj; + return start == other.start && end == other.end && negated == other.negated; + } + + /** + *

              Gets a hashCode compatible with the equals method.

              + * + * @return a suitable hashCode + */ + @Override + public int hashCode() { + return 83 + start + 7 * end + (negated ? 1 : 0); + } + + /** + *

              Gets a string representation of the character range.

              + * + * @return string representation of this range + */ + @Override + public String toString() { + if (iToString == null) { + final StringBuilder buf = new StringBuilder(4); + if (isNegated()) { + buf.append('^'); + } + buf.append(start); + if (start != end) { + buf.append('-'); + buf.append(end); + } + iToString = buf.toString(); + } + return iToString; + } + + // Expansions + //----------------------------------------------------------------------- + /** + *

              Returns an iterator which can be used to walk through the characters described by this range.

              + * + *

              #NotThreadSafe# the iterator is not thread-safe

              + * @return an iterator to the chars represented by this range + * @since 2.5 + */ + @Override + public Iterator iterator() { + return new CharacterIterator(this); + } + + /** + * Character {@link Iterator}. + *

              #NotThreadSafe#

              + */ + private static class CharacterIterator implements Iterator { + /** The current character */ + private char current; + + private final CharRange range; + private boolean hasNext; + + /** + * Construct a new iterator for the character range. + * + * @param r The character range + */ + private CharacterIterator(final CharRange r) { + range = r; + hasNext = true; + + if (range.negated) { + if (range.start == 0) { + if (range.end == Character.MAX_VALUE) { + // This range is an empty set + hasNext = false; + } else { + current = (char) (range.end + 1); + } + } else { + current = 0; + } + } else { + current = range.start; + } + } + + /** + * Prepare the next character in the range. + */ + private void prepareNext() { + if (range.negated) { + if (current == Character.MAX_VALUE) { + hasNext = false; + } else if (current + 1 == range.start) { + if (range.end == Character.MAX_VALUE) { + hasNext = false; + } else { + current = (char) (range.end + 1); + } + } else { + current = (char) (current + 1); + } + } else if (current < range.end) { + current = (char) (current + 1); + } else { + hasNext = false; + } + } + + /** + * Has the iterator not reached the end character yet? + * + * @return {@code true} if the iterator has yet to reach the character date + */ + @Override + public boolean hasNext() { + return hasNext; + } + + /** + * Return the next character in the iteration + * + * @return {@code Character} for the next character + */ + @Override + public Character next() { + if (hasNext == false) { + throw new NoSuchElementException(); + } + final char cur = current; + prepareNext(); + return Character.valueOf(cur); + } + + /** + * Always throws UnsupportedOperationException. + * + * @throws UnsupportedOperationException + * @see java.util.Iterator#remove() + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSequenceUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSequenceUtils.java new file mode 100644 index 000000000..5d10f4836 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSequenceUtils.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +/** + *

              Operations on {@link CharSequence} that are + * {@code null} safe.

              + * + * @see CharSequence + * @since 3.0 + * @version $Id: CharSequenceUtils.java 1606051 2014-06-27 12:22:17Z ggregory $ + */ +public class CharSequenceUtils { + + private static final int NOT_FOUND = -1; + + /** + *

              {@code CharSequenceUtils} instances should NOT be constructed in + * standard programming.

              + * + *

              This constructor is public to permit tools that require a JavaBean + * instance to operate.

              + */ + public CharSequenceUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + *

              Returns a new {@code CharSequence} that is a subsequence of this + * sequence starting with the {@code char} value at the specified index.

              + * + *

              This provides the {@code CharSequence} equivalent to {@link String#substring(int)}. + * The length (in {@code char}) of the returned sequence is {@code length() - start}, + * so if {@code start == end} then an empty sequence is returned.

              + * + * @param cs the specified subsequence, null returns null + * @param start the start index, inclusive, valid + * @return a new subsequence, may be null + * @throws IndexOutOfBoundsException if {@code start} is negative or if + * {@code start} is greater than {@code length()} + */ + public static CharSequence subSequence(final CharSequence cs, final int start) { + return cs == null ? null : cs.subSequence(start, cs.length()); + } + + //----------------------------------------------------------------------- + /** + *

              Finds the first index in the {@code CharSequence} that matches the + * specified character.

              + * + * @param cs the {@code CharSequence} to be processed, not null + * @param searchChar the char to be searched for + * @param start the start index, negative starts at the string start + * @return the index where the search char was found, -1 if not found + */ + static int indexOf(final CharSequence cs, final int searchChar, int start) { + if (cs instanceof String) { + return ((String) cs).indexOf(searchChar, start); + } + final int sz = cs.length(); + if (start < 0) { + start = 0; + } + for (int i = start; i < sz; i++) { + if (cs.charAt(i) == searchChar) { + return i; + } + } + return NOT_FOUND; + } + + /** + * Used by the indexOf(CharSequence methods) as a green implementation of indexOf. + * + * @param cs the {@code CharSequence} to be processed + * @param searchChar the {@code CharSequence} to be searched for + * @param start the start index + * @return the index where the search sequence was found + */ + static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) { + return cs.toString().indexOf(searchChar.toString(), start); +// if (cs instanceof String && searchChar instanceof String) { +// // TODO: Do we assume searchChar is usually relatively small; +// // If so then calling toString() on it is better than reverting to +// // the green implementation in the else block +// return ((String) cs).indexOf((String) searchChar, start); +// } else { +// // TODO: Implement rather than convert to String +// return cs.toString().indexOf(searchChar.toString(), start); +// } + } + + /** + *

              Finds the last index in the {@code CharSequence} that matches the + * specified character.

              + * + * @param cs the {@code CharSequence} to be processed + * @param searchChar the char to be searched for + * @param start the start index, negative returns -1, beyond length starts at end + * @return the index where the search char was found, -1 if not found + */ + static int lastIndexOf(final CharSequence cs, final int searchChar, int start) { + if (cs instanceof String) { + return ((String) cs).lastIndexOf(searchChar, start); + } + final int sz = cs.length(); + if (start < 0) { + return NOT_FOUND; + } + if (start >= sz) { + start = sz - 1; + } + for (int i = start; i >= 0; --i) { + if (cs.charAt(i) == searchChar) { + return i; + } + } + return NOT_FOUND; + } + + /** + * Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf + * + * @param cs the {@code CharSequence} to be processed + * @param searchChar the {@code CharSequence} to be searched for + * @param start the start index + * @return the index where the search sequence was found + */ + static int lastIndexOf(final CharSequence cs, final CharSequence searchChar, final int start) { + return cs.toString().lastIndexOf(searchChar.toString(), start); +// if (cs instanceof String && searchChar instanceof String) { +// // TODO: Do we assume searchChar is usually relatively small; +// // If so then calling toString() on it is better than reverting to +// // the green implementation in the else block +// return ((String) cs).lastIndexOf((String) searchChar, start); +// } else { +// // TODO: Implement rather than convert to String +// return cs.toString().lastIndexOf(searchChar.toString(), start); +// } + } + + /** + * Green implementation of toCharArray. + * + * @param cs the {@code CharSequence} to be processed + * @return the resulting char array + */ + static char[] toCharArray(final CharSequence cs) { + if (cs instanceof String) { + return ((String) cs).toCharArray(); + } + final int sz = cs.length(); + final char[] array = new char[cs.length()]; + for (int i = 0; i < sz; i++) { + array[i] = cs.charAt(i); + } + return array; + } + + /** + * Green implementation of regionMatches. + * + * @param cs the {@code CharSequence} to be processed + * @param ignoreCase whether or not to be case insensitive + * @param thisStart the index to start on the {@code cs} CharSequence + * @param substring the {@code CharSequence} to be looked for + * @param start the index to start on the {@code substring} CharSequence + * @param length character length of the region + * @return whether the region matched + */ + static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, + final CharSequence substring, final int start, final int length) { + if (cs instanceof String && substring instanceof String) { + return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length); + } + int index1 = thisStart; + int index2 = start; + int tmpLen = length; + + while (tmpLen-- > 0) { + final char c1 = cs.charAt(index1++); + final char c2 = substring.charAt(index2++); + + if (c1 == c2) { + continue; + } + + if (!ignoreCase) { + return false; + } + + // The same check as in String.regionMatches(): + if (Character.toUpperCase(c1) != Character.toUpperCase(c2) + && Character.toLowerCase(c1) != Character.toLowerCase(c2)) { + return false; + } + } + + return true; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSet.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSet.java new file mode 100644 index 000000000..fc70e4317 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSet.java @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + *

              A set of characters.

              + * + *

              Instances are immutable, but instances of subclasses may not be.

              + * + *

              #ThreadSafe#

              + * @since 1.0 + * @version $Id: CharSet.java 1436770 2013-01-22 07:09:45Z ggregory $ + */ +public class CharSet implements Serializable { + + /** + * Required for serialization support. Lang version 2.0. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 5947847346149275958L; + + /** + * A CharSet defining no characters. + * @since 2.0 + */ + public static final CharSet EMPTY = new CharSet((String) null); + + /** + * A CharSet defining ASCII alphabetic characters "a-zA-Z". + * @since 2.0 + */ + public static final CharSet ASCII_ALPHA = new CharSet("a-zA-Z"); + + /** + * A CharSet defining ASCII alphabetic characters "a-z". + * @since 2.0 + */ + public static final CharSet ASCII_ALPHA_LOWER = new CharSet("a-z"); + + /** + * A CharSet defining ASCII alphabetic characters "A-Z". + * @since 2.0 + */ + public static final CharSet ASCII_ALPHA_UPPER = new CharSet("A-Z"); + + /** + * A CharSet defining ASCII alphabetic characters "0-9". + * @since 2.0 + */ + public static final CharSet ASCII_NUMERIC = new CharSet("0-9"); + + /** + * A Map of the common cases used in the factory. + * Subclasses can add more common patterns if desired + * @since 2.0 + */ + protected static final Map COMMON = Collections.synchronizedMap(new HashMap()); + + static { + COMMON.put(null, EMPTY); + COMMON.put("", EMPTY); + COMMON.put("a-zA-Z", ASCII_ALPHA); + COMMON.put("A-Za-z", ASCII_ALPHA); + COMMON.put("a-z", ASCII_ALPHA_LOWER); + COMMON.put("A-Z", ASCII_ALPHA_UPPER); + COMMON.put("0-9", ASCII_NUMERIC); + } + + /** The set of CharRange objects. */ + private final Set set = Collections.synchronizedSet(new HashSet()); + + //----------------------------------------------------------------------- + /** + *

              Factory method to create a new CharSet using a special syntax.

              + * + *
                + *
              • {@code null} or empty string ("") + * - set containing no characters
              • + *
              • Single character, such as "a" + * - set containing just that character
              • + *
              • Multi character, such as "a-e" + * - set containing characters from one character to the other
              • + *
              • Negated, such as "^a" or "^a-e" + * - set containing all characters except those defined
              • + *
              • Combinations, such as "abe-g" + * - set containing all the characters from the individual sets
              • + *
              + * + *

              The matching order is:

              + *
                + *
              1. Negated multi character range, such as "^a-e" + *
              2. Ordinary multi character range, such as "a-e" + *
              3. Negated single character, such as "^a" + *
              4. Ordinary single character, such as "a" + *
              + *

              Matching works left to right. Once a match is found the + * search starts again from the next character.

              + * + *

              If the same range is defined twice using the same syntax, only + * one range will be kept. + * Thus, "a-ca-c" creates only one range of "a-c".

              + * + *

              If the start and end of a range are in the wrong order, + * they are reversed. Thus "a-e" is the same as "e-a". + * As a result, "a-ee-a" would create only one range, + * as the "a-e" and "e-a" are the same.

              + * + *

              The set of characters represented is the union of the specified ranges.

              + * + *

              All CharSet objects returned by this method will be immutable.

              + * + * @param setStrs Strings to merge into the set, may be null + * @return a CharSet instance + * @since 2.4 + */ + public static CharSet getInstance(final String... setStrs) { + if (setStrs == null) { + return null; + } + if (setStrs.length == 1) { + final CharSet common = COMMON.get(setStrs[0]); + if (common != null) { + return common; + } + } + return new CharSet(setStrs); + } + + //----------------------------------------------------------------------- + /** + *

              Constructs a new CharSet using the set syntax. + * Each string is merged in with the set.

              + * + * @param set Strings to merge into the initial set + * @throws NullPointerException if set is {@code null} + */ + protected CharSet(final String... set) { + super(); + final int sz = set.length; + for (int i = 0; i < sz; i++) { + add(set[i]); + } + } + + //----------------------------------------------------------------------- + /** + *

              Add a set definition string to the {@code CharSet}.

              + * + * @param str set definition string + */ + protected void add(final String str) { + if (str == null) { + return; + } + + final int len = str.length(); + int pos = 0; + while (pos < len) { + final int remainder = len - pos; + if (remainder >= 4 && str.charAt(pos) == '^' && str.charAt(pos + 2) == '-') { + // negated range + set.add(CharRange.isNotIn(str.charAt(pos + 1), str.charAt(pos + 3))); + pos += 4; + } else if (remainder >= 3 && str.charAt(pos + 1) == '-') { + // range + set.add(CharRange.isIn(str.charAt(pos), str.charAt(pos + 2))); + pos += 3; + } else if (remainder >= 2 && str.charAt(pos) == '^') { + // negated char + set.add(CharRange.isNot(str.charAt(pos + 1))); + pos += 2; + } else { + // char + set.add(CharRange.is(str.charAt(pos))); + pos += 1; + } + } + } + + //----------------------------------------------------------------------- + /** + *

              Gets the internal set as an array of CharRange objects.

              + * + * @return an array of immutable CharRange objects + * @since 2.0 + */ +// NOTE: This is no longer public as CharRange is no longer a public class. +// It may be replaced when CharSet moves to Range. + /*public*/ CharRange[] getCharRanges() { + return set.toArray(new CharRange[set.size()]); + } + + //----------------------------------------------------------------------- + /** + *

              Does the {@code CharSet} contain the specified + * character {@code ch}.

              + * + * @param ch the character to check for + * @return {@code true} if the set contains the characters + */ + public boolean contains(final char ch) { + for (final CharRange range : set) { + if (range.contains(ch)) { + return true; + } + } + return false; + } + + // Basics + //----------------------------------------------------------------------- + /** + *

              Compares two {@code CharSet} objects, returning true if they represent + * exactly the same set of characters defined in the same way.

              + * + *

              The two sets {@code abc} and {@code a-c} are not + * equal according to this method.

              + * + * @param obj the object to compare to + * @return true if equal + * @since 2.0 + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof CharSet == false) { + return false; + } + final CharSet other = (CharSet) obj; + return set.equals(other.set); + } + + /** + *

              Gets a hash code compatible with the equals method.

              + * + * @return a suitable hash code + * @since 2.0 + */ + @Override + public int hashCode() { + return 89 + set.hashCode(); + } + + /** + *

              Gets a string representation of the set.

              + * + * @return string representation of the set + */ + @Override + public String toString() { + return set.toString(); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSetUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSetUtils.java new file mode 100644 index 000000000..90f21bd14 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/CharSetUtils.java @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +/** + *

              Operations on {@code CharSet} instances.

              + * + *

              This class handles {@code null} input gracefully. + * An exception will not be thrown for a {@code null} input. + * Each method documents its behaviour in more detail.

              + * + *

              #ThreadSafe#

              + * @see CharSet + * @since 1.0 + * @version $Id: CharSetUtils.java 1552679 2013-12-20 14:08:03Z britter $ + */ +public class CharSetUtils { + + /** + *

              CharSetUtils instances should NOT be constructed in standard programming. + * Instead, the class should be used as {@code CharSetUtils.evaluateSet(null);}.

              + * + *

              This constructor is public to permit tools that require a JavaBean instance + * to operate.

              + */ + public CharSetUtils() { + super(); + } + + // Squeeze + //----------------------------------------------------------------------- + /** + *

              Squeezes any repetitions of a character that is mentioned in the + * supplied set.

              + * + *
              +     * CharSetUtils.squeeze(null, *)        = null
              +     * CharSetUtils.squeeze("", *)          = ""
              +     * CharSetUtils.squeeze(*, null)        = *
              +     * CharSetUtils.squeeze(*, "")          = *
              +     * CharSetUtils.squeeze("hello", "k-p") = "helo"
              +     * CharSetUtils.squeeze("hello", "a-e") = "hello"
              +     * 
              + * + * @see CharSet#getInstance(java.lang.String...) for set-syntax. + * @param str the string to squeeze, may be null + * @param set the character set to use for manipulation, may be null + * @return the modified String, {@code null} if null string input + */ + public static String squeeze(final String str, final String... set) { + if (StringUtils.isEmpty(str) || deepEmpty(set)) { + return str; + } + final CharSet chars = CharSet.getInstance(set); + final StringBuilder buffer = new StringBuilder(str.length()); + final char[] chrs = str.toCharArray(); + final int sz = chrs.length; + char lastChar = ' '; + char ch = ' '; + for (int i = 0; i < sz; i++) { + ch = chrs[i]; + // Compare with contains() last for performance. + if (ch == lastChar && i != 0 && chars.contains(ch)) { + continue; + } + buffer.append(ch); + lastChar = ch; + } + return buffer.toString(); + } + + // ContainsAny + //----------------------------------------------------------------------- + /** + *

              Takes an argument in set-syntax, see evaluateSet, + * and identifies whether any of the characters are present in the specified string.

              + * + *
              +     * CharSetUtils.containsAny(null, *)        = false
              +     * CharSetUtils.containsAny("", *)          = false
              +     * CharSetUtils.containsAny(*, null)        = false
              +     * CharSetUtils.containsAny(*, "")          = false
              +     * CharSetUtils.containsAny("hello", "k-p") = true
              +     * CharSetUtils.containsAny("hello", "a-d") = false
              +     * 
              + * + * @see CharSet#getInstance(java.lang.String...) for set-syntax. + * @param str String to look for characters in, may be null + * @param set String[] set of characters to identify, may be null + * @return whether or not the characters in the set are in the primary string + * @since 3.2 + */ + public static boolean containsAny(final String str, final String... set) { + if (StringUtils.isEmpty(str) || deepEmpty(set)) { + return false; + } + final CharSet chars = CharSet.getInstance(set); + for (final char c : str.toCharArray()) { + if (chars.contains(c)) { + return true; + } + } + return false; + } + + // Count + //----------------------------------------------------------------------- + /** + *

              Takes an argument in set-syntax, see evaluateSet, + * and returns the number of characters present in the specified string.

              + * + *
              +     * CharSetUtils.count(null, *)        = 0
              +     * CharSetUtils.count("", *)          = 0
              +     * CharSetUtils.count(*, null)        = 0
              +     * CharSetUtils.count(*, "")          = 0
              +     * CharSetUtils.count("hello", "k-p") = 3
              +     * CharSetUtils.count("hello", "a-e") = 1
              +     * 
              + * + * @see CharSet#getInstance(java.lang.String...) for set-syntax. + * @param str String to count characters in, may be null + * @param set String[] set of characters to count, may be null + * @return the character count, zero if null string input + */ + public static int count(final String str, final String... set) { + if (StringUtils.isEmpty(str) || deepEmpty(set)) { + return 0; + } + final CharSet chars = CharSet.getInstance(set); + int count = 0; + for (final char c : str.toCharArray()) { + if (chars.contains(c)) { + count++; + } + } + return count; + } + + // Keep + //----------------------------------------------------------------------- + /** + *

              Takes an argument in set-syntax, see evaluateSet, + * and keeps any of characters present in the specified string.

              + * + *
              +     * CharSetUtils.keep(null, *)        = null
              +     * CharSetUtils.keep("", *)          = ""
              +     * CharSetUtils.keep(*, null)        = ""
              +     * CharSetUtils.keep(*, "")          = ""
              +     * CharSetUtils.keep("hello", "hl")  = "hll"
              +     * CharSetUtils.keep("hello", "le")  = "ell"
              +     * 
              + * + * @see CharSet#getInstance(java.lang.String...) for set-syntax. + * @param str String to keep characters from, may be null + * @param set String[] set of characters to keep, may be null + * @return the modified String, {@code null} if null string input + * @since 2.0 + */ + public static String keep(final String str, final String... set) { + if (str == null) { + return null; + } + if (str.isEmpty() || deepEmpty(set)) { + return StringUtils.EMPTY; + } + return modify(str, set, true); + } + + // Delete + //----------------------------------------------------------------------- + /** + *

              Takes an argument in set-syntax, see evaluateSet, + * and deletes any of characters present in the specified string.

              + * + *
              +     * CharSetUtils.delete(null, *)        = null
              +     * CharSetUtils.delete("", *)          = ""
              +     * CharSetUtils.delete(*, null)        = *
              +     * CharSetUtils.delete(*, "")          = *
              +     * CharSetUtils.delete("hello", "hl")  = "eo"
              +     * CharSetUtils.delete("hello", "le")  = "ho"
              +     * 
              + * + * @see CharSet#getInstance(java.lang.String...) for set-syntax. + * @param str String to delete characters from, may be null + * @param set String[] set of characters to delete, may be null + * @return the modified String, {@code null} if null string input + */ + public static String delete(final String str, final String... set) { + if (StringUtils.isEmpty(str) || deepEmpty(set)) { + return str; + } + return modify(str, set, false); + } + + //----------------------------------------------------------------------- + /** + * Implementation of delete and keep + * + * @param str String to modify characters within + * @param set String[] set of characters to modify + * @param expect whether to evaluate on match, or non-match + * @return the modified String, not null + */ + private static String modify(final String str, final String[] set, final boolean expect) { + final CharSet chars = CharSet.getInstance(set); + final StringBuilder buffer = new StringBuilder(str.length()); + final char[] chrs = str.toCharArray(); + final int sz = chrs.length; + for(int i=0; iOperations on char primitives and Character objects.

              + * + *

              This class tries to handle {@code null} input gracefully. + * An exception will not be thrown for a {@code null} input. + * Each method documents its behaviour in more detail.

              + * + *

              #ThreadSafe#

              + * @since 2.1 + * @version $Id: CharUtils.java 1666535 2015-03-13 18:18:59Z britter $ + */ +public class CharUtils { + + private static final String[] CHAR_STRING_ARRAY = new String[128]; + + private static final char[] HEX_DIGITS = new char[] {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + + /** + * {@code \u000a} linefeed LF ('\n'). + * + * @see JLF: Escape Sequences + * for Character and String Literals + * @since 2.2 + */ + public static final char LF = '\n'; + + /** + * {@code \u000d} carriage return CR ('\r'). + * + * @see JLF: Escape Sequences + * for Character and String Literals + * @since 2.2 + */ + public static final char CR = '\r'; + + + static { + for (char c = 0; c < CHAR_STRING_ARRAY.length; c++) { + CHAR_STRING_ARRAY[c] = String.valueOf(c); + } + } + + /** + *

              {@code CharUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as {@code CharUtils.toString('c');}.

              + * + *

              This constructor is public to permit tools that require a JavaBean instance + * to operate.

              + */ + public CharUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + *

              Converts the character to a Character.

              + * + *

              For ASCII 7 bit characters, this uses a cache that will return the + * same Character object each time.

              + * + *
              +     *   CharUtils.toCharacterObject(' ')  = ' '
              +     *   CharUtils.toCharacterObject('A')  = 'A'
              +     * 
              + * + * @deprecated Java 5 introduced {@link Character#valueOf(char)} which caches chars 0 through 127. + * @param ch the character to convert + * @return a Character of the specified character + */ + @Deprecated + public static Character toCharacterObject(final char ch) { + return Character.valueOf(ch); + } + + /** + *

              Converts the String to a Character using the first character, returning + * null for empty Strings.

              + * + *

              For ASCII 7 bit characters, this uses a cache that will return the + * same Character object each time.

              + * + *
              +     *   CharUtils.toCharacterObject(null) = null
              +     *   CharUtils.toCharacterObject("")   = null
              +     *   CharUtils.toCharacterObject("A")  = 'A'
              +     *   CharUtils.toCharacterObject("BA") = 'B'
              +     * 
              + * + * @param str the character to convert + * @return the Character value of the first letter of the String + */ + public static Character toCharacterObject(final String str) { + if (StringUtils.isEmpty(str)) { + return null; + } + return Character.valueOf(str.charAt(0)); + } + + //----------------------------------------------------------------------- + /** + *

              Converts the Character to a char throwing an exception for {@code null}.

              + * + *
              +     *   CharUtils.toChar(' ')  = ' '
              +     *   CharUtils.toChar('A')  = 'A'
              +     *   CharUtils.toChar(null) throws IllegalArgumentException
              +     * 
              + * + * @param ch the character to convert + * @return the char value of the Character + * @throws IllegalArgumentException if the Character is null + */ + public static char toChar(final Character ch) { + if (ch == null) { + throw new IllegalArgumentException("The Character must not be null"); + } + return ch.charValue(); + } + + /** + *

              Converts the Character to a char handling {@code null}.

              + * + *
              +     *   CharUtils.toChar(null, 'X') = 'X'
              +     *   CharUtils.toChar(' ', 'X')  = ' '
              +     *   CharUtils.toChar('A', 'X')  = 'A'
              +     * 
              + * + * @param ch the character to convert + * @param defaultValue the value to use if the Character is null + * @return the char value of the Character or the default if null + */ + public static char toChar(final Character ch, final char defaultValue) { + if (ch == null) { + return defaultValue; + } + return ch.charValue(); + } + + //----------------------------------------------------------------------- + /** + *

              Converts the String to a char using the first character, throwing + * an exception on empty Strings.

              + * + *
              +     *   CharUtils.toChar("A")  = 'A'
              +     *   CharUtils.toChar("BA") = 'B'
              +     *   CharUtils.toChar(null) throws IllegalArgumentException
              +     *   CharUtils.toChar("")   throws IllegalArgumentException
              +     * 
              + * + * @param str the character to convert + * @return the char value of the first letter of the String + * @throws IllegalArgumentException if the String is empty + */ + public static char toChar(final String str) { + if (StringUtils.isEmpty(str)) { + throw new IllegalArgumentException("The String must not be empty"); + } + return str.charAt(0); + } + + /** + *

              Converts the String to a char using the first character, defaulting + * the value on empty Strings.

              + * + *
              +     *   CharUtils.toChar(null, 'X') = 'X'
              +     *   CharUtils.toChar("", 'X')   = 'X'
              +     *   CharUtils.toChar("A", 'X')  = 'A'
              +     *   CharUtils.toChar("BA", 'X') = 'B'
              +     * 
              + * + * @param str the character to convert + * @param defaultValue the value to use if the Character is null + * @return the char value of the first letter of the String or the default if null + */ + public static char toChar(final String str, final char defaultValue) { + if (StringUtils.isEmpty(str)) { + return defaultValue; + } + return str.charAt(0); + } + + //----------------------------------------------------------------------- + /** + *

              Converts the character to the Integer it represents, throwing an + * exception if the character is not numeric.

              + * + *

              This method coverts the char '1' to the int 1 and so on.

              + * + *
              +     *   CharUtils.toIntValue('3')  = 3
              +     *   CharUtils.toIntValue('A')  throws IllegalArgumentException
              +     * 
              + * + * @param ch the character to convert + * @return the int value of the character + * @throws IllegalArgumentException if the character is not ASCII numeric + */ + public static int toIntValue(final char ch) { + if (isAsciiNumeric(ch) == false) { + throw new IllegalArgumentException("The character " + ch + " is not in the range '0' - '9'"); + } + return ch - 48; + } + + /** + *

              Converts the character to the Integer it represents, throwing an + * exception if the character is not numeric.

              + * + *

              This method coverts the char '1' to the int 1 and so on.

              + * + *
              +     *   CharUtils.toIntValue('3', -1)  = 3
              +     *   CharUtils.toIntValue('A', -1)  = -1
              +     * 
              + * + * @param ch the character to convert + * @param defaultValue the default value to use if the character is not numeric + * @return the int value of the character + */ + public static int toIntValue(final char ch, final int defaultValue) { + if (isAsciiNumeric(ch) == false) { + return defaultValue; + } + return ch - 48; + } + + /** + *

              Converts the character to the Integer it represents, throwing an + * exception if the character is not numeric.

              + * + *

              This method coverts the char '1' to the int 1 and so on.

              + * + *
              +     *   CharUtils.toIntValue('3')  = 3
              +     *   CharUtils.toIntValue(null) throws IllegalArgumentException
              +     *   CharUtils.toIntValue('A')  throws IllegalArgumentException
              +     * 
              + * + * @param ch the character to convert, not null + * @return the int value of the character + * @throws IllegalArgumentException if the Character is not ASCII numeric or is null + */ + public static int toIntValue(final Character ch) { + if (ch == null) { + throw new IllegalArgumentException("The character must not be null"); + } + return toIntValue(ch.charValue()); + } + + /** + *

              Converts the character to the Integer it represents, throwing an + * exception if the character is not numeric.

              + * + *

              This method coverts the char '1' to the int 1 and so on.

              + * + *
              +     *   CharUtils.toIntValue(null, -1) = -1
              +     *   CharUtils.toIntValue('3', -1)  = 3
              +     *   CharUtils.toIntValue('A', -1)  = -1
              +     * 
              + * + * @param ch the character to convert + * @param defaultValue the default value to use if the character is not numeric + * @return the int value of the character + */ + public static int toIntValue(final Character ch, final int defaultValue) { + if (ch == null) { + return defaultValue; + } + return toIntValue(ch.charValue(), defaultValue); + } + + //----------------------------------------------------------------------- + /** + *

              Converts the character to a String that contains the one character.

              + * + *

              For ASCII 7 bit characters, this uses a cache that will return the + * same String object each time.

              + * + *
              +     *   CharUtils.toString(' ')  = " "
              +     *   CharUtils.toString('A')  = "A"
              +     * 
              + * + * @param ch the character to convert + * @return a String containing the one specified character + */ + public static String toString(final char ch) { + if (ch < 128) { + return CHAR_STRING_ARRAY[ch]; + } + return new String(new char[] {ch}); + } + + /** + *

              Converts the character to a String that contains the one character.

              + * + *

              For ASCII 7 bit characters, this uses a cache that will return the + * same String object each time.

              + * + *

              If {@code null} is passed in, {@code null} will be returned.

              + * + *
              +     *   CharUtils.toString(null) = null
              +     *   CharUtils.toString(' ')  = " "
              +     *   CharUtils.toString('A')  = "A"
              +     * 
              + * + * @param ch the character to convert + * @return a String containing the one specified character + */ + public static String toString(final Character ch) { + if (ch == null) { + return null; + } + return toString(ch.charValue()); + } + + //-------------------------------------------------------------------------- + /** + *

              Converts the string to the Unicode format '\u0020'.

              + * + *

              This format is the Java source code format.

              + * + *
              +     *   CharUtils.unicodeEscaped(' ') = "\u0020"
              +     *   CharUtils.unicodeEscaped('A') = "\u0041"
              +     * 
              + * + * @param ch the character to convert + * @return the escaped Unicode string + */ + public static String unicodeEscaped(final char ch) { + StringBuilder sb = new StringBuilder(6); + sb.append("\\u"); + sb.append(HEX_DIGITS[(ch >> 12) & 15]); + sb.append(HEX_DIGITS[(ch >> 8) & 15]); + sb.append(HEX_DIGITS[(ch >> 4) & 15]); + sb.append(HEX_DIGITS[(ch) & 15]); + return sb.toString(); + } + + /** + *

              Converts the string to the Unicode format '\u0020'.

              + * + *

              This format is the Java source code format.

              + * + *

              If {@code null} is passed in, {@code null} will be returned.

              + * + *
              +     *   CharUtils.unicodeEscaped(null) = null
              +     *   CharUtils.unicodeEscaped(' ')  = "\u0020"
              +     *   CharUtils.unicodeEscaped('A')  = "\u0041"
              +     * 
              + * + * @param ch the character to convert, may be null + * @return the escaped Unicode string, null if null input + */ + public static String unicodeEscaped(final Character ch) { + if (ch == null) { + return null; + } + return unicodeEscaped(ch.charValue()); + } + + //-------------------------------------------------------------------------- + /** + *

              Checks whether the character is ASCII 7 bit.

              + * + *
              +     *   CharUtils.isAscii('a')  = true
              +     *   CharUtils.isAscii('A')  = true
              +     *   CharUtils.isAscii('3')  = true
              +     *   CharUtils.isAscii('-')  = true
              +     *   CharUtils.isAscii('\n') = true
              +     *   CharUtils.isAscii('©') = false
              +     * 
              + * + * @param ch the character to check + * @return true if less than 128 + */ + public static boolean isAscii(final char ch) { + return ch < 128; + } + + /** + *

              Checks whether the character is ASCII 7 bit printable.

              + * + *
              +     *   CharUtils.isAsciiPrintable('a')  = true
              +     *   CharUtils.isAsciiPrintable('A')  = true
              +     *   CharUtils.isAsciiPrintable('3')  = true
              +     *   CharUtils.isAsciiPrintable('-')  = true
              +     *   CharUtils.isAsciiPrintable('\n') = false
              +     *   CharUtils.isAsciiPrintable('©') = false
              +     * 
              + * + * @param ch the character to check + * @return true if between 32 and 126 inclusive + */ + public static boolean isAsciiPrintable(final char ch) { + return ch >= 32 && ch < 127; + } + + /** + *

              Checks whether the character is ASCII 7 bit control.

              + * + *
              +     *   CharUtils.isAsciiControl('a')  = false
              +     *   CharUtils.isAsciiControl('A')  = false
              +     *   CharUtils.isAsciiControl('3')  = false
              +     *   CharUtils.isAsciiControl('-')  = false
              +     *   CharUtils.isAsciiControl('\n') = true
              +     *   CharUtils.isAsciiControl('©') = false
              +     * 
              + * + * @param ch the character to check + * @return true if less than 32 or equals 127 + */ + public static boolean isAsciiControl(final char ch) { + return ch < 32 || ch == 127; + } + + /** + *

              Checks whether the character is ASCII 7 bit alphabetic.

              + * + *
              +     *   CharUtils.isAsciiAlpha('a')  = true
              +     *   CharUtils.isAsciiAlpha('A')  = true
              +     *   CharUtils.isAsciiAlpha('3')  = false
              +     *   CharUtils.isAsciiAlpha('-')  = false
              +     *   CharUtils.isAsciiAlpha('\n') = false
              +     *   CharUtils.isAsciiAlpha('©') = false
              +     * 
              + * + * @param ch the character to check + * @return true if between 65 and 90 or 97 and 122 inclusive + */ + public static boolean isAsciiAlpha(final char ch) { + return isAsciiAlphaUpper(ch) || isAsciiAlphaLower(ch); + } + + /** + *

              Checks whether the character is ASCII 7 bit alphabetic upper case.

              + * + *
              +     *   CharUtils.isAsciiAlphaUpper('a')  = false
              +     *   CharUtils.isAsciiAlphaUpper('A')  = true
              +     *   CharUtils.isAsciiAlphaUpper('3')  = false
              +     *   CharUtils.isAsciiAlphaUpper('-')  = false
              +     *   CharUtils.isAsciiAlphaUpper('\n') = false
              +     *   CharUtils.isAsciiAlphaUpper('©') = false
              +     * 
              + * + * @param ch the character to check + * @return true if between 65 and 90 inclusive + */ + public static boolean isAsciiAlphaUpper(final char ch) { + return ch >= 'A' && ch <= 'Z'; + } + + /** + *

              Checks whether the character is ASCII 7 bit alphabetic lower case.

              + * + *
              +     *   CharUtils.isAsciiAlphaLower('a')  = true
              +     *   CharUtils.isAsciiAlphaLower('A')  = false
              +     *   CharUtils.isAsciiAlphaLower('3')  = false
              +     *   CharUtils.isAsciiAlphaLower('-')  = false
              +     *   CharUtils.isAsciiAlphaLower('\n') = false
              +     *   CharUtils.isAsciiAlphaLower('©') = false
              +     * 
              + * + * @param ch the character to check + * @return true if between 97 and 122 inclusive + */ + public static boolean isAsciiAlphaLower(final char ch) { + return ch >= 'a' && ch <= 'z'; + } + + /** + *

              Checks whether the character is ASCII 7 bit numeric.

              + * + *
              +     *   CharUtils.isAsciiNumeric('a')  = false
              +     *   CharUtils.isAsciiNumeric('A')  = false
              +     *   CharUtils.isAsciiNumeric('3')  = true
              +     *   CharUtils.isAsciiNumeric('-')  = false
              +     *   CharUtils.isAsciiNumeric('\n') = false
              +     *   CharUtils.isAsciiNumeric('©') = false
              +     * 
              + * + * @param ch the character to check + * @return true if between 48 and 57 inclusive + */ + public static boolean isAsciiNumeric(final char ch) { + return ch >= '0' && ch <= '9'; + } + + /** + *

              Checks whether the character is ASCII 7 bit numeric.

              + * + *
              +     *   CharUtils.isAsciiAlphanumeric('a')  = true
              +     *   CharUtils.isAsciiAlphanumeric('A')  = true
              +     *   CharUtils.isAsciiAlphanumeric('3')  = true
              +     *   CharUtils.isAsciiAlphanumeric('-')  = false
              +     *   CharUtils.isAsciiAlphanumeric('\n') = false
              +     *   CharUtils.isAsciiAlphanumeric('©') = false
              +     * 
              + * + * @param ch the character to check + * @return true if between 48 and 57 or 65 and 90 or 97 and 122 inclusive + */ + public static boolean isAsciiAlphanumeric(final char ch) { + return isAsciiAlpha(ch) || isAsciiNumeric(ch); + } + + /** + *

              Compares two {@code char} values numerically. This is the same functionality as provided in Java 7.

              + * + * @param x the first {@code char} to compare + * @param y the second {@code char} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 + */ + public static int compare(char x, char y) { + return x-y; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ClassPathUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ClassPathUtils.java new file mode 100644 index 000000000..477809b90 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ClassPathUtils.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +/** + * Operations regarding the classpath. + * + *

              The methods of this class do not allow {@code null} inputs.

              + * + * @since 3.3 + * @version $Id: ClassPathUtils.java 1606051 2014-06-27 12:22:17Z ggregory $ + */ +//@Immutable +public class ClassPathUtils { + + /** + *

              {@code ClassPathUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used as + * {@code ClassPathUtils.toFullyQualifiedName(MyClass.class, "MyClass.properties");}.

              + * + *

              This constructor is public to permit tools that require a JavaBean + * instance to operate.

              + */ + public ClassPathUtils() { + super(); + } + + /** + * Returns the fully qualified name for the resource with name {@code resourceName} relative to the given context. + * + *

              Note that this method does not check whether the resource actually exists. + * It only constructs the name. + * Null inputs are not allowed.

              + * + *
              +     * ClassPathUtils.toFullyQualifiedName(StringUtils.class, "StringUtils.properties") = "org.apache.commons.lang3.StringUtils.properties"
              +     * 
              + * + * @param context The context for constructing the name. + * @param resourceName the resource name to construct the fully qualified name for. + * @return the fully qualified name of the resource with name {@code resourceName}. + * @throws java.lang.NullPointerException if either {@code context} or {@code resourceName} is null. + */ + public static String toFullyQualifiedName(final Class context, final String resourceName) { + Validate.notNull(context, "Parameter '%s' must not be null!", "context" ); + Validate.notNull(resourceName, "Parameter '%s' must not be null!", "resourceName"); + return toFullyQualifiedName(context.getPackage(), resourceName); + } + + /** + * Returns the fully qualified name for the resource with name {@code resourceName} relative to the given context. + * + *

              Note that this method does not check whether the resource actually exists. + * It only constructs the name. + * Null inputs are not allowed.

              + * + *
              +     * ClassPathUtils.toFullyQualifiedName(StringUtils.class.getPackage(), "StringUtils.properties") = "org.apache.commons.lang3.StringUtils.properties"
              +     * 
              + * + * @param context The context for constructing the name. + * @param resourceName the resource name to construct the fully qualified name for. + * @return the fully qualified name of the resource with name {@code resourceName}. + * @throws java.lang.NullPointerException if either {@code context} or {@code resourceName} is null. + */ + public static String toFullyQualifiedName(final Package context, final String resourceName) { + Validate.notNull(context, "Parameter '%s' must not be null!", "context" ); + Validate.notNull(resourceName, "Parameter '%s' must not be null!", "resourceName"); + final StringBuilder sb = new StringBuilder(); + sb.append(context.getName()); + sb.append("."); + sb.append(resourceName); + return sb.toString(); + } + + /** + * Returns the fully qualified path for the resource with name {@code resourceName} relative to the given context. + * + *

              Note that this method does not check whether the resource actually exists. + * It only constructs the path. + * Null inputs are not allowed.

              + * + *
              +     * ClassPathUtils.toFullyQualifiedPath(StringUtils.class, "StringUtils.properties") = "org/apache/commons/lang3/StringUtils.properties"
              +     * 
              + * + * @param context The context for constructing the path. + * @param resourceName the resource name to construct the fully qualified path for. + * @return the fully qualified path of the resource with name {@code resourceName}. + * @throws java.lang.NullPointerException if either {@code context} or {@code resourceName} is null. + */ + public static String toFullyQualifiedPath(final Class context, final String resourceName) { + Validate.notNull(context, "Parameter '%s' must not be null!", "context" ); + Validate.notNull(resourceName, "Parameter '%s' must not be null!", "resourceName"); + return toFullyQualifiedPath(context.getPackage(), resourceName); + } + + + /** + * Returns the fully qualified path for the resource with name {@code resourceName} relative to the given context. + * + *

              Note that this method does not check whether the resource actually exists. + * It only constructs the path. + * Null inputs are not allowed.

              + * + *
              +     * ClassPathUtils.toFullyQualifiedPath(StringUtils.class.getPackage(), "StringUtils.properties") = "org/apache/commons/lang3/StringUtils.properties"
              +     * 
              + * + * @param context The context for constructing the path. + * @param resourceName the resource name to construct the fully qualified path for. + * @return the fully qualified path of the resource with name {@code resourceName}. + * @throws java.lang.NullPointerException if either {@code context} or {@code resourceName} is null. + */ + public static String toFullyQualifiedPath(final Package context, final String resourceName) { + Validate.notNull(context, "Parameter '%s' must not be null!", "context" ); + Validate.notNull(resourceName, "Parameter '%s' must not be null!", "resourceName"); + final StringBuilder sb = new StringBuilder(); + sb.append(context.getName().replace('.', '/')); + sb.append("/"); + sb.append(resourceName); + return sb.toString(); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ClassUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ClassUtils.java new file mode 100644 index 000000000..5392b606c --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ClassUtils.java @@ -0,0 +1,1320 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.fr.third.org.apache.commons.lang3.mutable.MutableObject; + +/** + *

              Operates on classes without using reflection.

              + * + *

              This class handles invalid {@code null} inputs as best it can. + * Each method documents its behaviour in more detail.

              + * + *

              The notion of a {@code canonical name} includes the human + * readable name for the type, for example {@code int[]}. The + * non-canonical method variants work with the JVM names, such as + * {@code [I}.

              + * + * @since 2.0 + * @version $Id: ClassUtils.java 1669311 2015-03-26 10:24:19Z britter $ + */ +public class ClassUtils { + /** + * Inclusivity literals for {@link #hierarchy(Class, Interfaces)}. + * @since 3.2 + */ + public enum Interfaces { + INCLUDE, EXCLUDE + } + + /** + * The package separator character: '.' == {@value}. + */ + public static final char PACKAGE_SEPARATOR_CHAR = '.'; + + /** + * The package separator String: ".". + */ + public static final String PACKAGE_SEPARATOR = String.valueOf(PACKAGE_SEPARATOR_CHAR); + + /** + * The inner class separator character: '$' == {@value}. + */ + public static final char INNER_CLASS_SEPARATOR_CHAR = '$'; + + /** + * The inner class separator String: {@code "$"}. + */ + public static final String INNER_CLASS_SEPARATOR = String.valueOf(INNER_CLASS_SEPARATOR_CHAR); + + /** + * Maps primitive {@code Class}es to their corresponding wrapper {@code Class}. + */ + private static final Map, Class> primitiveWrapperMap = new HashMap, Class>(); + static { + primitiveWrapperMap.put(Boolean.TYPE, Boolean.class); + primitiveWrapperMap.put(Byte.TYPE, Byte.class); + primitiveWrapperMap.put(Character.TYPE, Character.class); + primitiveWrapperMap.put(Short.TYPE, Short.class); + primitiveWrapperMap.put(Integer.TYPE, Integer.class); + primitiveWrapperMap.put(Long.TYPE, Long.class); + primitiveWrapperMap.put(Double.TYPE, Double.class); + primitiveWrapperMap.put(Float.TYPE, Float.class); + primitiveWrapperMap.put(Void.TYPE, Void.TYPE); + } + + /** + * Maps wrapper {@code Class}es to their corresponding primitive types. + */ + private static final Map, Class> wrapperPrimitiveMap = new HashMap, Class>(); + static { + for (final Class primitiveClass : primitiveWrapperMap.keySet()) { + final Class wrapperClass = primitiveWrapperMap.get(primitiveClass); + if (!primitiveClass.equals(wrapperClass)) { + wrapperPrimitiveMap.put(wrapperClass, primitiveClass); + } + } + } + + /** + * Maps a primitive class name to its corresponding abbreviation used in array class names. + */ + private static final Map abbreviationMap; + + /** + * Maps an abbreviation used in array class names to corresponding primitive class name. + */ + private static final Map reverseAbbreviationMap; + + /** + * Feed abbreviation maps + */ + static { + final Map m = new HashMap(); + m.put("int", "I"); + m.put("boolean", "Z"); + m.put("float", "F"); + m.put("long", "J"); + m.put("short", "S"); + m.put("byte", "B"); + m.put("double", "D"); + m.put("char", "C"); + m.put("void", "V"); + final Map r = new HashMap(); + for (final Map.Entry e : m.entrySet()) { + r.put(e.getValue(), e.getKey()); + } + abbreviationMap = Collections.unmodifiableMap(m); + reverseAbbreviationMap = Collections.unmodifiableMap(r); + } + + /** + *

              ClassUtils instances should NOT be constructed in standard programming. + * Instead, the class should be used as + * {@code ClassUtils.getShortClassName(cls)}.

              + * + *

              This constructor is public to permit tools that require a JavaBean + * instance to operate.

              + */ + public ClassUtils() { + super(); + } + + // Short class name + // ---------------------------------------------------------------------- + /** + *

              Gets the class name minus the package name for an {@code Object}.

              + * + * @param object the class to get the short name for, may be null + * @param valueIfNull the value to return if null + * @return the class name of the object without the package name, or the null value + */ + public static String getShortClassName(final Object object, final String valueIfNull) { + if (object == null) { + return valueIfNull; + } + return getShortClassName(object.getClass()); + } + + /** + *

              Gets the class name minus the package name from a {@code Class}.

              + * + *

              Consider using the Java 5 API {@link Class#getSimpleName()} instead. + * The one known difference is that this code will return {@code "Map.Entry"} while + * the {@code java.lang.Class} variant will simply return {@code "Entry"}.

              + * + * @param cls the class to get the short name for. + * @return the class name without the package name or an empty string + */ + public static String getShortClassName(final Class cls) { + if (cls == null) { + return StringUtils.EMPTY; + } + return getShortClassName(cls.getName()); + } + + /** + *

              Gets the class name minus the package name from a String.

              + * + *

              The string passed in is assumed to be a class name - it is not checked.

              + + *

              Note that this method differs from Class.getSimpleName() in that this will + * return {@code "Map.Entry"} whilst the {@code java.lang.Class} variant will simply + * return {@code "Entry"}.

              + * + * @param className the className to get the short name for + * @return the class name of the class without the package name or an empty string + */ + public static String getShortClassName(String className) { + if (StringUtils.isEmpty(className)) { + return StringUtils.EMPTY; + } + + final StringBuilder arrayPrefix = new StringBuilder(); + + // Handle array encoding + if (className.startsWith("[")) { + while (className.charAt(0) == '[') { + className = className.substring(1); + arrayPrefix.append("[]"); + } + // Strip Object type encoding + if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') { + className = className.substring(1, className.length() - 1); + } + + if (reverseAbbreviationMap.containsKey(className)) { + className = reverseAbbreviationMap.get(className); + } + } + + final int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR); + final int innerIdx = className.indexOf( + INNER_CLASS_SEPARATOR_CHAR, lastDotIdx == -1 ? 0 : lastDotIdx + 1); + String out = className.substring(lastDotIdx + 1); + if (innerIdx != -1) { + out = out.replace(INNER_CLASS_SEPARATOR_CHAR, PACKAGE_SEPARATOR_CHAR); + } + return out + arrayPrefix; + } + + /** + *

              Null-safe version of aClass.getSimpleName()

              + * + * @param cls the class for which to get the simple name. + * @return the simple class name. + * @since 3.0 + * @see Class#getSimpleName() + */ + public static String getSimpleName(final Class cls) { + if (cls == null) { + return StringUtils.EMPTY; + } + return cls.getSimpleName(); + } + + /** + *

              Null-safe version of aClass.getSimpleName()

              + * + * @param object the object for which to get the simple class name. + * @param valueIfNull the value to return if object is null + * @return the simple class name. + * @since 3.0 + * @see Class#getSimpleName() + */ + public static String getSimpleName(final Object object, final String valueIfNull) { + if (object == null) { + return valueIfNull; + } + return getSimpleName(object.getClass()); + } + + // Package name + // ---------------------------------------------------------------------- + /** + *

              Gets the package name of an {@code Object}.

              + * + * @param object the class to get the package name for, may be null + * @param valueIfNull the value to return if null + * @return the package name of the object, or the null value + */ + public static String getPackageName(final Object object, final String valueIfNull) { + if (object == null) { + return valueIfNull; + } + return getPackageName(object.getClass()); + } + + /** + *

              Gets the package name of a {@code Class}.

              + * + * @param cls the class to get the package name for, may be {@code null}. + * @return the package name or an empty string + */ + public static String getPackageName(final Class cls) { + if (cls == null) { + return StringUtils.EMPTY; + } + return getPackageName(cls.getName()); + } + + /** + *

              Gets the package name from a {@code String}.

              + * + *

              The string passed in is assumed to be a class name - it is not checked.

              + *

              If the class is unpackaged, return an empty string.

              + * + * @param className the className to get the package name for, may be {@code null} + * @return the package name or an empty string + */ + public static String getPackageName(String className) { + if (StringUtils.isEmpty(className)) { + return StringUtils.EMPTY; + } + + // Strip array encoding + while (className.charAt(0) == '[') { + className = className.substring(1); + } + // Strip Object type encoding + if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') { + className = className.substring(1); + } + + final int i = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR); + if (i == -1) { + return StringUtils.EMPTY; + } + return className.substring(0, i); + } + + // Abbreviated name + // ---------------------------------------------------------------------- + /** + *

              Gets the abbreviated name of a {@code Class}.

              + * + * @param cls the class to get the abbreviated name for, may be {@code null} + * @param len the desired length of the abbreviated name + * @return the abbreviated name or an empty string + * @throws IllegalArgumentException if len <= 0 + * @see #getAbbreviatedName(String, int) + * @since 3.4 + */ + public static String getAbbreviatedName(final Class cls, int len) { + if (cls == null) { + return StringUtils.EMPTY; + } + return getAbbreviatedName(cls.getName(), len); + } + + /** + *

              Gets the abbreviated class name from a {@code String}.

              + * + *

              The string passed in is assumed to be a class name - it is not checked.

              + * + *

              The abbreviation algorithm will shorten the class name, usually without + * significant loss of meaning.

              + *

              The abbreviated class name will always include the complete package hierarchy. + * If enough space is available, rightmost sub-packages will be displayed in full + * length.

              + * + *

              The following table illustrates the algorithm:

              + * + * + * + * + * + * + *
              classNamelenreturn
              null 1""
              "java.lang.String" 5"j.l.String"
              "java.lang.String"15"j.lang.String"
              "java.lang.String"30"java.lang.String"
              + * @param className the className to get the abbreviated name for, may be {@code null} + * @param len the desired length of the abbreviated name + * @return the abbreviated name or an empty string + * @throws IllegalArgumentException if len <= 0 + * @since 3.4 + */ + public static String getAbbreviatedName(String className, int len) { + if (len <= 0) { + throw new IllegalArgumentException("len must be > 0"); + } + if (className == null) { + return StringUtils.EMPTY; + } + + int availableSpace = len; + int packageLevels = StringUtils.countMatches(className, '.'); + String[] output = new String[packageLevels + 1]; + int endIndex = className.length() - 1; + for (int level = packageLevels; level >= 0; level--) { + int startIndex = className.lastIndexOf('.', endIndex); + String part = className.substring(startIndex + 1, endIndex + 1); + availableSpace -= part.length(); + if (level > 0) { + // all elements except top level require an additional char space + availableSpace--; + } + if (level == packageLevels) { + // ClassName is always complete + output[level] = part; + } else { + if (availableSpace > 0) { + output[level] = part; + } else { + // if no space is left still the first char is used + output[level] = part.substring(0, 1); + } + } + endIndex = startIndex - 1; + } + + return StringUtils.join(output, '.'); + } + + // Superclasses/Superinterfaces + // ---------------------------------------------------------------------- + /** + *

              Gets a {@code List} of superclasses for the given class.

              + * + * @param cls the class to look up, may be {@code null} + * @return the {@code List} of superclasses in order going up from this one + * {@code null} if null input + */ + public static List> getAllSuperclasses(final Class cls) { + if (cls == null) { + return null; + } + final List> classes = new ArrayList>(); + Class superclass = cls.getSuperclass(); + while (superclass != null) { + classes.add(superclass); + superclass = superclass.getSuperclass(); + } + return classes; + } + + /** + *

              Gets a {@code List} of all interfaces implemented by the given + * class and its superclasses.

              + * + *

              The order is determined by looking through each interface in turn as + * declared in the source file and following its hierarchy up. Then each + * superclass is considered in the same way. Later duplicates are ignored, + * so the order is maintained.

              + * + * @param cls the class to look up, may be {@code null} + * @return the {@code List} of interfaces in order, + * {@code null} if null input + */ + public static List> getAllInterfaces(final Class cls) { + if (cls == null) { + return null; + } + + final LinkedHashSet> interfacesFound = new LinkedHashSet>(); + getAllInterfaces(cls, interfacesFound); + + return new ArrayList>(interfacesFound); + } + + /** + * Get the interfaces for the specified class. + * + * @param cls the class to look up, may be {@code null} + * @param interfacesFound the {@code Set} of interfaces for the class + */ + private static void getAllInterfaces(Class cls, final HashSet> interfacesFound) { + while (cls != null) { + final Class[] interfaces = cls.getInterfaces(); + + for (final Class i : interfaces) { + if (interfacesFound.add(i)) { + getAllInterfaces(i, interfacesFound); + } + } + + cls = cls.getSuperclass(); + } + } + + // Convert list + // ---------------------------------------------------------------------- + /** + *

              Given a {@code List} of class names, this method converts them into classes.

              + * + *

              A new {@code List} is returned. If the class name cannot be found, {@code null} + * is stored in the {@code List}. If the class name in the {@code List} is + * {@code null}, {@code null} is stored in the output {@code List}.

              + * + * @param classNames the classNames to change + * @return a {@code List} of Class objects corresponding to the class names, + * {@code null} if null input + * @throws ClassCastException if classNames contains a non String entry + */ + public static List> convertClassNamesToClasses(final List classNames) { + if (classNames == null) { + return null; + } + final List> classes = new ArrayList>(classNames.size()); + for (final String className : classNames) { + try { + classes.add(Class.forName(className)); + } catch (final Exception ex) { + classes.add(null); + } + } + return classes; + } + + /** + *

              Given a {@code List} of {@code Class} objects, this method converts + * them into class names.

              + * + *

              A new {@code List} is returned. {@code null} objects will be copied into + * the returned list as {@code null}.

              + * + * @param classes the classes to change + * @return a {@code List} of class names corresponding to the Class objects, + * {@code null} if null input + * @throws ClassCastException if {@code classes} contains a non-{@code Class} entry + */ + public static List convertClassesToClassNames(final List> classes) { + if (classes == null) { + return null; + } + final List classNames = new ArrayList(classes.size()); + for (final Class cls : classes) { + if (cls == null) { + classNames.add(null); + } else { + classNames.add(cls.getName()); + } + } + return classNames; + } + + // Is assignable + // ---------------------------------------------------------------------- + /** + *

              Checks if an array of Classes can be assigned to another array of Classes.

              + * + *

              This method calls {@link #isAssignable(Class, Class) isAssignable} for each + * Class pair in the input arrays. It can be used to check if a set of arguments + * (the first parameter) are suitably compatible with a set of method parameter types + * (the second parameter).

              + * + *

              Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this + * method takes into account widenings of primitive classes and + * {@code null}s.

              + * + *

              Primitive widenings allow an int to be assigned to a {@code long}, + * {@code float} or {@code double}. This method returns the correct + * result for these cases.

              + * + *

              {@code Null} may be assigned to any reference type. This method will + * return {@code true} if {@code null} is passed in and the toClass is + * non-primitive.

              + * + *

              Specifically, this method tests whether the type represented by the + * specified {@code Class} parameter can be converted to the type + * represented by this {@code Class} object via an identity conversion + * widening primitive or widening reference conversion. See + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

              + * + *

              Since Lang 3.0, this method will default behavior for + * calculating assignability between primitive and wrapper types corresponding + * to the running Java version; i.e. autoboxing will be the default + * behavior in VMs running Java versions > 1.5.

              + * + * @param classArray the array of Classes to check, may be {@code null} + * @param toClassArray the array of Classes to try to assign into, may be {@code null} + * @return {@code true} if assignment possible + */ + public static boolean isAssignable(final Class[] classArray, final Class... toClassArray) { + return isAssignable(classArray, toClassArray, SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_5)); + } + + /** + *

              Checks if an array of Classes can be assigned to another array of Classes.

              + * + *

              This method calls {@link #isAssignable(Class, Class) isAssignable} for each + * Class pair in the input arrays. It can be used to check if a set of arguments + * (the first parameter) are suitably compatible with a set of method parameter types + * (the second parameter).

              + * + *

              Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this + * method takes into account widenings of primitive classes and + * {@code null}s.

              + * + *

              Primitive widenings allow an int to be assigned to a {@code long}, + * {@code float} or {@code double}. This method returns the correct + * result for these cases.

              + * + *

              {@code Null} may be assigned to any reference type. This method will + * return {@code true} if {@code null} is passed in and the toClass is + * non-primitive.

              + * + *

              Specifically, this method tests whether the type represented by the + * specified {@code Class} parameter can be converted to the type + * represented by this {@code Class} object via an identity conversion + * widening primitive or widening reference conversion. See + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

              + * + * @param classArray the array of Classes to check, may be {@code null} + * @param toClassArray the array of Classes to try to assign into, may be {@code null} + * @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers + * @return {@code true} if assignment possible + */ + public static boolean isAssignable(Class[] classArray, Class[] toClassArray, final boolean autoboxing) { + if (ArrayUtils.isSameLength(classArray, toClassArray) == false) { + return false; + } + if (classArray == null) { + classArray = ArrayUtils.EMPTY_CLASS_ARRAY; + } + if (toClassArray == null) { + toClassArray = ArrayUtils.EMPTY_CLASS_ARRAY; + } + for (int i = 0; i < classArray.length; i++) { + if (isAssignable(classArray[i], toClassArray[i], autoboxing) == false) { + return false; + } + } + return true; + } + + /** + * Returns whether the given {@code type} is a primitive or primitive wrapper ({@link Boolean}, {@link Byte}, {@link Character}, + * {@link Short}, {@link Integer}, {@link Long}, {@link Double}, {@link Float}). + * + * @param type + * The class to query or null. + * @return true if the given {@code type} is a primitive or primitive wrapper ({@link Boolean}, {@link Byte}, {@link Character}, + * {@link Short}, {@link Integer}, {@link Long}, {@link Double}, {@link Float}). + * @since 3.1 + */ + public static boolean isPrimitiveOrWrapper(final Class type) { + if (type == null) { + return false; + } + return type.isPrimitive() || isPrimitiveWrapper(type); + } + + /** + * Returns whether the given {@code type} is a primitive wrapper ({@link Boolean}, {@link Byte}, {@link Character}, {@link Short}, + * {@link Integer}, {@link Long}, {@link Double}, {@link Float}). + * + * @param type + * The class to query or null. + * @return true if the given {@code type} is a primitive wrapper ({@link Boolean}, {@link Byte}, {@link Character}, {@link Short}, + * {@link Integer}, {@link Long}, {@link Double}, {@link Float}). + * @since 3.1 + */ + public static boolean isPrimitiveWrapper(final Class type) { + return wrapperPrimitiveMap.containsKey(type); + } + + /** + *

              Checks if one {@code Class} can be assigned to a variable of + * another {@code Class}.

              + * + *

              Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, + * this method takes into account widenings of primitive classes and + * {@code null}s.

              + * + *

              Primitive widenings allow an int to be assigned to a long, float or + * double. This method returns the correct result for these cases.

              + * + *

              {@code Null} may be assigned to any reference type. This method + * will return {@code true} if {@code null} is passed in and the + * toClass is non-primitive.

              + * + *

              Specifically, this method tests whether the type represented by the + * specified {@code Class} parameter can be converted to the type + * represented by this {@code Class} object via an identity conversion + * widening primitive or widening reference conversion. See + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

              + * + *

              Since Lang 3.0, this method will default behavior for + * calculating assignability between primitive and wrapper types corresponding + * to the running Java version; i.e. autoboxing will be the default + * behavior in VMs running Java versions > 1.5.

              + * + * @param cls the Class to check, may be null + * @param toClass the Class to try to assign into, returns false if null + * @return {@code true} if assignment possible + */ + public static boolean isAssignable(final Class cls, final Class toClass) { + return isAssignable(cls, toClass, SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_5)); + } + + /** + *

              Checks if one {@code Class} can be assigned to a variable of + * another {@code Class}.

              + * + *

              Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, + * this method takes into account widenings of primitive classes and + * {@code null}s.

              + * + *

              Primitive widenings allow an int to be assigned to a long, float or + * double. This method returns the correct result for these cases.

              + * + *

              {@code Null} may be assigned to any reference type. This method + * will return {@code true} if {@code null} is passed in and the + * toClass is non-primitive.

              + * + *

              Specifically, this method tests whether the type represented by the + * specified {@code Class} parameter can be converted to the type + * represented by this {@code Class} object via an identity conversion + * widening primitive or widening reference conversion. See + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

              + * + * @param cls the Class to check, may be null + * @param toClass the Class to try to assign into, returns false if null + * @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers + * @return {@code true} if assignment possible + */ + public static boolean isAssignable(Class cls, final Class toClass, final boolean autoboxing) { + if (toClass == null) { + return false; + } + // have to check for null, as isAssignableFrom doesn't + if (cls == null) { + return !toClass.isPrimitive(); + } + //autoboxing: + if (autoboxing) { + if (cls.isPrimitive() && !toClass.isPrimitive()) { + cls = primitiveToWrapper(cls); + if (cls == null) { + return false; + } + } + if (toClass.isPrimitive() && !cls.isPrimitive()) { + cls = wrapperToPrimitive(cls); + if (cls == null) { + return false; + } + } + } + if (cls.equals(toClass)) { + return true; + } + if (cls.isPrimitive()) { + if (toClass.isPrimitive() == false) { + return false; + } + if (Integer.TYPE.equals(cls)) { + return Long.TYPE.equals(toClass) + || Float.TYPE.equals(toClass) + || Double.TYPE.equals(toClass); + } + if (Long.TYPE.equals(cls)) { + return Float.TYPE.equals(toClass) + || Double.TYPE.equals(toClass); + } + if (Boolean.TYPE.equals(cls)) { + return false; + } + if (Double.TYPE.equals(cls)) { + return false; + } + if (Float.TYPE.equals(cls)) { + return Double.TYPE.equals(toClass); + } + if (Character.TYPE.equals(cls)) { + return Integer.TYPE.equals(toClass) + || Long.TYPE.equals(toClass) + || Float.TYPE.equals(toClass) + || Double.TYPE.equals(toClass); + } + if (Short.TYPE.equals(cls)) { + return Integer.TYPE.equals(toClass) + || Long.TYPE.equals(toClass) + || Float.TYPE.equals(toClass) + || Double.TYPE.equals(toClass); + } + if (Byte.TYPE.equals(cls)) { + return Short.TYPE.equals(toClass) + || Integer.TYPE.equals(toClass) + || Long.TYPE.equals(toClass) + || Float.TYPE.equals(toClass) + || Double.TYPE.equals(toClass); + } + // should never get here + return false; + } + return toClass.isAssignableFrom(cls); + } + + /** + *

              Converts the specified primitive Class object to its corresponding + * wrapper Class object.

              + * + *

              NOTE: From v2.2, this method handles {@code Void.TYPE}, + * returning {@code Void.TYPE}.

              + * + * @param cls the class to convert, may be null + * @return the wrapper class for {@code cls} or {@code cls} if + * {@code cls} is not a primitive. {@code null} if null input. + * @since 2.1 + */ + public static Class primitiveToWrapper(final Class cls) { + Class convertedClass = cls; + if (cls != null && cls.isPrimitive()) { + convertedClass = primitiveWrapperMap.get(cls); + } + return convertedClass; + } + + /** + *

              Converts the specified array of primitive Class objects to an array of + * its corresponding wrapper Class objects.

              + * + * @param classes the class array to convert, may be null or empty + * @return an array which contains for each given class, the wrapper class or + * the original class if class is not a primitive. {@code null} if null input. + * Empty array if an empty array passed in. + * @since 2.1 + */ + public static Class[] primitivesToWrappers(final Class... classes) { + if (classes == null) { + return null; + } + + if (classes.length == 0) { + return classes; + } + + final Class[] convertedClasses = new Class[classes.length]; + for (int i = 0; i < classes.length; i++) { + convertedClasses[i] = primitiveToWrapper(classes[i]); + } + return convertedClasses; + } + + /** + *

              Converts the specified wrapper class to its corresponding primitive + * class.

              + * + *

              This method is the counter part of {@code primitiveToWrapper()}. + * If the passed in class is a wrapper class for a primitive type, this + * primitive type will be returned (e.g. {@code Integer.TYPE} for + * {@code Integer.class}). For other classes, or if the parameter is + * null, the return value is null.

              + * + * @param cls the class to convert, may be null + * @return the corresponding primitive type if {@code cls} is a + * wrapper class, null otherwise + * @see #primitiveToWrapper(Class) + * @since 2.4 + */ + public static Class wrapperToPrimitive(final Class cls) { + return wrapperPrimitiveMap.get(cls); + } + + /** + *

              Converts the specified array of wrapper Class objects to an array of + * its corresponding primitive Class objects.

              + * + *

              This method invokes {@code wrapperToPrimitive()} for each element + * of the passed in array.

              + * + * @param classes the class array to convert, may be null or empty + * @return an array which contains for each given class, the primitive class or + * null if the original class is not a wrapper class. {@code null} if null input. + * Empty array if an empty array passed in. + * @see #wrapperToPrimitive(Class) + * @since 2.4 + */ + public static Class[] wrappersToPrimitives(final Class... classes) { + if (classes == null) { + return null; + } + + if (classes.length == 0) { + return classes; + } + + final Class[] convertedClasses = new Class[classes.length]; + for (int i = 0; i < classes.length; i++) { + convertedClasses[i] = wrapperToPrimitive(classes[i]); + } + return convertedClasses; + } + + // Inner class + // ---------------------------------------------------------------------- + /** + *

              Is the specified class an inner class or static nested class.

              + * + * @param cls the class to check, may be null + * @return {@code true} if the class is an inner or static nested class, + * false if not or {@code null} + */ + public static boolean isInnerClass(final Class cls) { + return cls != null && cls.getEnclosingClass() != null; + } + + // Class loading + // ---------------------------------------------------------------------- + /** + * Returns the class represented by {@code className} using the + * {@code classLoader}. This implementation supports the syntaxes + * "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}", + * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}". + * + * @param classLoader the class loader to use to load the class + * @param className the class name + * @param initialize whether the class must be initialized + * @return the class represented by {@code className} using the {@code classLoader} + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass( + final ClassLoader classLoader, final String className, final boolean initialize) throws ClassNotFoundException { + try { + Class clazz; + if (abbreviationMap.containsKey(className)) { + final String clsName = "[" + abbreviationMap.get(className); + clazz = Class.forName(clsName, initialize, classLoader).getComponentType(); + } else { + clazz = Class.forName(toCanonicalName(className), initialize, classLoader); + } + return clazz; + } catch (final ClassNotFoundException ex) { + // allow path separators (.) as inner class name separators + final int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR); + + if (lastDotIndex != -1) { + try { + return getClass(classLoader, className.substring(0, lastDotIndex) + + INNER_CLASS_SEPARATOR_CHAR + className.substring(lastDotIndex + 1), + initialize); + } catch (final ClassNotFoundException ex2) { // NOPMD + // ignore exception + } + } + + throw ex; + } + } + + /** + * Returns the (initialized) class represented by {@code className} + * using the {@code classLoader}. This implementation supports + * the syntaxes "{@code java.util.Map.Entry[]}", + * "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}", + * and "{@code [Ljava.util.Map$Entry;}". + * + * @param classLoader the class loader to use to load the class + * @param className the class name + * @return the class represented by {@code className} using the {@code classLoader} + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass(final ClassLoader classLoader, final String className) throws ClassNotFoundException { + return getClass(classLoader, className, true); + } + + /** + * Returns the (initialized) class represented by {@code className} + * using the current thread's context class loader. This implementation + * supports the syntaxes "{@code java.util.Map.Entry[]}", + * "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}", + * and "{@code [Ljava.util.Map$Entry;}". + * + * @param className the class name + * @return the class represented by {@code className} using the current thread's context class loader + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass(final String className) throws ClassNotFoundException { + return getClass(className, true); + } + + /** + * Returns the class represented by {@code className} using the + * current thread's context class loader. This implementation supports the + * syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}", + * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}". + * + * @param className the class name + * @param initialize whether the class must be initialized + * @return the class represented by {@code className} using the current thread's context class loader + * @throws ClassNotFoundException if the class is not found + */ + public static Class getClass(final String className, final boolean initialize) throws ClassNotFoundException { + final ClassLoader contextCL = Thread.currentThread().getContextClassLoader(); + final ClassLoader loader = contextCL == null ? ClassUtils.class.getClassLoader() : contextCL; + return getClass(loader, className, initialize); + } + + // Public method + // ---------------------------------------------------------------------- + /** + *

              Returns the desired Method much like {@code Class.getMethod}, however + * it ensures that the returned Method is from a public class or interface and not + * from an anonymous inner class. This means that the Method is invokable and + * doesn't fall foul of Java bug + * 4071957).

              + * + *
              +     *  Set set = Collections.unmodifiableSet(...);
              +     *  Method method = ClassUtils.getPublicMethod(set.getClass(), "isEmpty",  new Class[0]);
              +     *  Object result = method.invoke(set, new Object[]);
              +     *  
              + * + * @param cls the class to check, not null + * @param methodName the name of the method + * @param parameterTypes the list of parameters + * @return the method + * @throws NullPointerException if the class is null + * @throws SecurityException if a security violation occurred + * @throws NoSuchMethodException if the method is not found in the given class + * or if the method doesn't conform with the requirements + */ + public static Method getPublicMethod(final Class cls, final String methodName, final Class... parameterTypes) + throws SecurityException, NoSuchMethodException { + + final Method declaredMethod = cls.getMethod(methodName, parameterTypes); + if (Modifier.isPublic(declaredMethod.getDeclaringClass().getModifiers())) { + return declaredMethod; + } + + final List> candidateClasses = new ArrayList>(); + candidateClasses.addAll(getAllInterfaces(cls)); + candidateClasses.addAll(getAllSuperclasses(cls)); + + for (final Class candidateClass : candidateClasses) { + if (!Modifier.isPublic(candidateClass.getModifiers())) { + continue; + } + Method candidateMethod; + try { + candidateMethod = candidateClass.getMethod(methodName, parameterTypes); + } catch (final NoSuchMethodException ex) { + continue; + } + if (Modifier.isPublic(candidateMethod.getDeclaringClass().getModifiers())) { + return candidateMethod; + } + } + + throw new NoSuchMethodException("Can't find a public method for " + + methodName + " " + ArrayUtils.toString(parameterTypes)); + } + + // ---------------------------------------------------------------------- + /** + * Converts a class name to a JLS style class name. + * + * @param className the class name + * @return the converted name + */ + private static String toCanonicalName(String className) { + className = StringUtils.deleteWhitespace(className); + if (className == null) { + throw new NullPointerException("className must not be null."); + } else if (className.endsWith("[]")) { + final StringBuilder classNameBuffer = new StringBuilder(); + while (className.endsWith("[]")) { + className = className.substring(0, className.length() - 2); + classNameBuffer.append("["); + } + final String abbreviation = abbreviationMap.get(className); + if (abbreviation != null) { + classNameBuffer.append(abbreviation); + } else { + classNameBuffer.append("L").append(className).append(";"); + } + className = classNameBuffer.toString(); + } + return className; + } + + /** + *

              Converts an array of {@code Object} in to an array of {@code Class} objects. + * If any of these objects is null, a null element will be inserted into the array.

              + * + *

              This method returns {@code null} for a {@code null} input array.

              + * + * @param array an {@code Object} array + * @return a {@code Class} array, {@code null} if null array input + * @since 2.4 + */ + public static Class[] toClass(final Object... array) { + if (array == null) { + return null; + } else if (array.length == 0) { + return ArrayUtils.EMPTY_CLASS_ARRAY; + } + final Class[] classes = new Class[array.length]; + for (int i = 0; i < array.length; i++) { + classes[i] = array[i] == null ? null : array[i].getClass(); + } + return classes; + } + + // Short canonical name + // ---------------------------------------------------------------------- + /** + *

              Gets the canonical name minus the package name for an {@code Object}.

              + * + * @param object the class to get the short name for, may be null + * @param valueIfNull the value to return if null + * @return the canonical name of the object without the package name, or the null value + * @since 2.4 + */ + public static String getShortCanonicalName(final Object object, final String valueIfNull) { + if (object == null) { + return valueIfNull; + } + return getShortCanonicalName(object.getClass().getName()); + } + + /** + *

              Gets the canonical name minus the package name from a {@code Class}.

              + * + * @param cls the class to get the short name for. + * @return the canonical name without the package name or an empty string + * @since 2.4 + */ + public static String getShortCanonicalName(final Class cls) { + if (cls == null) { + return StringUtils.EMPTY; + } + return getShortCanonicalName(cls.getName()); + } + + /** + *

              Gets the canonical name minus the package name from a String.

              + * + *

              The string passed in is assumed to be a canonical name - it is not checked.

              + * + * @param canonicalName the class name to get the short name for + * @return the canonical name of the class without the package name or an empty string + * @since 2.4 + */ + public static String getShortCanonicalName(final String canonicalName) { + return ClassUtils.getShortClassName(getCanonicalName(canonicalName)); + } + + // Package name + // ---------------------------------------------------------------------- + /** + *

              Gets the package name from the canonical name of an {@code Object}.

              + * + * @param object the class to get the package name for, may be null + * @param valueIfNull the value to return if null + * @return the package name of the object, or the null value + * @since 2.4 + */ + public static String getPackageCanonicalName(final Object object, final String valueIfNull) { + if (object == null) { + return valueIfNull; + } + return getPackageCanonicalName(object.getClass().getName()); + } + + /** + *

              Gets the package name from the canonical name of a {@code Class}.

              + * + * @param cls the class to get the package name for, may be {@code null}. + * @return the package name or an empty string + * @since 2.4 + */ + public static String getPackageCanonicalName(final Class cls) { + if (cls == null) { + return StringUtils.EMPTY; + } + return getPackageCanonicalName(cls.getName()); + } + + /** + *

              Gets the package name from the canonical name.

              + * + *

              The string passed in is assumed to be a canonical name - it is not checked.

              + *

              If the class is unpackaged, return an empty string.

              + * + * @param canonicalName the canonical name to get the package name for, may be {@code null} + * @return the package name or an empty string + * @since 2.4 + */ + public static String getPackageCanonicalName(final String canonicalName) { + return ClassUtils.getPackageName(getCanonicalName(canonicalName)); + } + + /** + *

              Converts a given name of class into canonical format. + * If name of class is not a name of array class it returns + * unchanged name.

              + *

              Example: + *

                + *
              • {@code getCanonicalName("[I") = "int[]"}
              • + *
              • {@code getCanonicalName("[Ljava.lang.String;") = "java.lang.String[]"}
              • + *
              • {@code getCanonicalName("java.lang.String") = "java.lang.String"}
              • + *
              + *

              + * + * @param className the name of class + * @return canonical form of class name + * @since 2.4 + */ + private static String getCanonicalName(String className) { + className = StringUtils.deleteWhitespace(className); + if (className == null) { + return null; + } + int dim = 0; + while (className.startsWith("[")) { + dim++; + className = className.substring(1); + } + if (dim < 1) { + return className; + } + if (className.startsWith("L")) { + className = className.substring( + 1, + className.endsWith(";") + ? className.length() - 1 + : className.length()); + } else { + if (className.length() > 0) { + className = reverseAbbreviationMap.get(className.substring(0, 1)); + } + } + final StringBuilder canonicalClassNameBuffer = new StringBuilder(className); + for (int i = 0; i < dim; i++) { + canonicalClassNameBuffer.append("[]"); + } + return canonicalClassNameBuffer.toString(); + } + + /** + * Get an {@link Iterable} that can iterate over a class hierarchy in ascending (subclass to superclass) order, + * excluding interfaces. + * + * @param type the type to get the class hierarchy from + * @return Iterable an Iterable over the class hierarchy of the given class + * @since 3.2 + */ + public static Iterable> hierarchy(final Class type) { + return hierarchy(type, Interfaces.EXCLUDE); + } + + /** + * Get an {@link Iterable} that can iterate over a class hierarchy in ascending (subclass to superclass) order. + * + * @param type the type to get the class hierarchy from + * @param interfacesBehavior switch indicating whether to include or exclude interfaces + * @return Iterable an Iterable over the class hierarchy of the given class + * @since 3.2 + */ + public static Iterable> hierarchy(final Class type, final Interfaces interfacesBehavior) { + final Iterable> classes = new Iterable>() { + + @Override + public Iterator> iterator() { + final MutableObject> next = new MutableObject>(type); + return new Iterator>() { + + @Override + public boolean hasNext() { + return next.getValue() != null; + } + + @Override + public Class next() { + final Class result = next.getValue(); + next.setValue(result.getSuperclass()); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + + }; + if (interfacesBehavior != Interfaces.INCLUDE) { + return classes; + } + return new Iterable>() { + + @Override + public Iterator> iterator() { + final Set> seenInterfaces = new HashSet>(); + final Iterator> wrapped = classes.iterator(); + + return new Iterator>() { + Iterator> interfaces = Collections.> emptySet().iterator(); + + @Override + public boolean hasNext() { + return interfaces.hasNext() || wrapped.hasNext(); + } + + @Override + public Class next() { + if (interfaces.hasNext()) { + final Class nextInterface = interfaces.next(); + seenInterfaces.add(nextInterface); + return nextInterface; + } + final Class nextSuperclass = wrapped.next(); + final Set> currentInterfaces = new LinkedHashSet>(); + walkInterfaces(currentInterfaces, nextSuperclass); + interfaces = currentInterfaces.iterator(); + return nextSuperclass; + } + + private void walkInterfaces(final Set> addTo, final Class c) { + for (final Class iface : c.getInterfaces()) { + if (!seenInterfaces.contains(iface)) { + addTo.add(iface); + } + walkInterfaces(addTo, iface); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + }; + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Conversion.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Conversion.java new file mode 100644 index 000000000..457db79a0 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Conversion.java @@ -0,0 +1,1594 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package com.fr.third.org.apache.commons.lang3; + +import java.util.UUID; + + +/** + *

              + * Static methods to convert a type into another, with endianness and bit ordering awareness. + *

              + *

              + * The methods names follow a naming rule:
              + * {@code [source endianness][source bit ordering]To[destination endianness][destination bit ordering]} + *

              + *

              + * Source/destination type fields is one of the following: + *

              + *
                + *
              • binary: an array of booleans
              • + *
              • byte or byteArray
              • + *
              • int or intArray
              • + *
              • long or longArray
              • + *
              • hex: a String containing hexadecimal digits (lowercase in destination)
              • + *
              • hexDigit: a Char containing a hexadecimal digit (lowercase in destination)
              • + *
              • uuid
              • + *
              + *

              + * Endianness field: little endian is the default, in this case the field is absent. In case of + * big endian, the field is "Be".
              Bit ordering: Lsb0 is the default, in this case the field + * is absent. In case of Msb0, the field is "Msb0". + *

              + *

              + * Example: intBeMsb0ToHex convert an int with big endian byte order and Msb0 bit order into its + * hexadecimal string representation + *

              + *

              + * Most of the methods provide only default encoding for destination, this limits the number of + * ways to do one thing. Unless you are dealing with data from/to outside of the JVM platform, + * you should not need to use "Be" and "Msb0" methods. + *

              + *

              + * Development status: work on going, only a part of the little endian, Lsb0 methods implemented + * so far. + *

              + * + * @since Lang 3.2 + * @version $Id: Conversion.java 1606449 2014-06-29 11:11:26Z ggregory $ + */ + +public class Conversion { + + private static final boolean[] TTTT = new boolean[] { true, true, true, true }; + private static final boolean[] FTTT = new boolean[] { false, true, true, true }; + private static final boolean[] TFTT = new boolean[] { true, false, true, true }; + private static final boolean[] FFTT = new boolean[] { false, false, true, true }; + private static final boolean[] TTFT = new boolean[] { true, true, false, true }; + private static final boolean[] FTFT = new boolean[] { false, true, false, true }; + private static final boolean[] TFFT = new boolean[] { true, false, false, true }; + private static final boolean[] FFFT = new boolean[] { false, false, false, true }; + private static final boolean[] TTTF = new boolean[] { true, true, true, false }; + private static final boolean[] FTTF = new boolean[] { false, true, true, false }; + private static final boolean[] TFTF = new boolean[] { true, false, true, false }; + private static final boolean[] FFTF = new boolean[] { false, false, true, false }; + private static final boolean[] TTFF = new boolean[] { true, true, false, false }; + private static final boolean[] FTFF = new boolean[] { false, true, false, false }; + private static final boolean[] TFFF = new boolean[] { true, false, false, false }; + private static final boolean[] FFFF = new boolean[] { false, false, false, false }; + + /** + *

              + * Converts a hexadecimal digit into an int using the default (Lsb0) bit ordering. + *

              + *

              + * '1' is converted to 1 + *

              + * + * @param hexDigit the hexadecimal digit to convert + * @return an int equals to {@code hexDigit} + * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + */ + public static int hexDigitToInt(final char hexDigit) { + final int digit = Character.digit(hexDigit, 16); + if (digit < 0) { + throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + } + return digit; + } + + /** + *

              + * Converts a hexadecimal digit into an int using the Msb0 bit ordering. + *

              + *

              + * '1' is converted to 8 + *

              + * + * @param hexDigit the hexadecimal digit to convert + * @return an int equals to {@code hexDigit} + * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + */ + public static int hexDigitMsb0ToInt(final char hexDigit) { + switch (hexDigit) { + case '0': + return 0x0; + case '1': + return 0x8; + case '2': + return 0x4; + case '3': + return 0xC; + case '4': + return 0x2; + case '5': + return 0xA; + case '6': + return 0x6; + case '7': + return 0xE; + case '8': + return 0x1; + case '9': + return 0x9; + case 'a':// fall through + case 'A': + return 0x5; + case 'b':// fall through + case 'B': + return 0xD; + case 'c':// fall through + case 'C': + return 0x3; + case 'd':// fall through + case 'D': + return 0xB; + case 'e':// fall through + case 'E': + return 0x7; + case 'f':// fall through + case 'F': + return 0xF; + default: + throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + } + } + + /** + *

              + * Converts a hexadecimal digit into binary (represented as boolean array) using the default + * (Lsb0) bit ordering. + *

              + *

              + * '1' is converted as follow: (1, 0, 0, 0) + *

              + * + * @param hexDigit the hexadecimal digit to convert + * @return a boolean array with the binary representation of {@code hexDigit} + * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + */ + public static boolean[] hexDigitToBinary(final char hexDigit) { + switch (hexDigit) { + case '0': + return FFFF.clone(); + case '1': + return TFFF.clone(); + case '2': + return FTFF.clone(); + case '3': + return TTFF.clone(); + case '4': + return FFTF.clone(); + case '5': + return TFTF.clone(); + case '6': + return FTTF.clone(); + case '7': + return TTTF.clone(); + case '8': + return FFFT.clone(); + case '9': + return TFFT.clone(); + case 'a':// fall through + case 'A': + return FTFT.clone(); + case 'b':// fall through + case 'B': + return TTFT.clone(); + case 'c':// fall through + case 'C': + return FFTT.clone(); + case 'd':// fall through + case 'D': + return TFTT.clone(); + case 'e':// fall through + case 'E': + return FTTT.clone(); + case 'f':// fall through + case 'F': + return TTTT.clone(); + default: + throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + } + } + + /** + *

              + * Converts a hexadecimal digit into binary (represented as boolean array) using the Msb0 + * bit ordering. + *

              + *

              + * '1' is converted as follow: (0, 0, 0, 1) + *

              + * + * @param hexDigit the hexadecimal digit to convert + * @return a boolean array with the binary representation of {@code hexDigit} + * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + */ + public static boolean[] hexDigitMsb0ToBinary(final char hexDigit) { + switch (hexDigit) { + case '0': + return FFFF.clone(); + case '1': + return FFFT.clone(); + case '2': + return FFTF.clone(); + case '3': + return FFTT.clone(); + case '4': + return FTFF.clone(); + case '5': + return FTFT.clone(); + case '6': + return FTTF.clone(); + case '7': + return FTTT.clone(); + case '8': + return TFFF.clone(); + case '9': + return TFFT.clone(); + case 'a':// fall through + case 'A': + return TFTF.clone(); + case 'b':// fall through + case 'B': + return TFTT.clone(); + case 'c':// fall through + case 'C': + return TTFF.clone(); + case 'd':// fall through + case 'D': + return TTFT.clone(); + case 'e':// fall through + case 'E': + return TTTF.clone(); + case 'f':// fall through + case 'F': + return TTTT.clone(); + default: + throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + } + } + + /** + *

              + * Converts binary (represented as boolean array) to a hexadecimal digit using the default + * (Lsb0) bit ordering. + *

              + *

              + * (1, 0, 0, 0) is converted as follow: '1' + *

              + * + * @param src the binary to convert + * @return a hexadecimal digit representing the selected bits + * @throws IllegalArgumentException if {@code src} is empty + * @throws NullPointerException if {@code src} is {@code null} + */ + public static char binaryToHexDigit(final boolean[] src) { + return binaryToHexDigit(src, 0); + } + + /** + *

              + * Converts binary (represented as boolean array) to a hexadecimal digit using the default + * (Lsb0) bit ordering. + *

              + *

              + * (1, 0, 0, 0) is converted as follow: '1' + *

              + * + * @param src the binary to convert + * @param srcPos the position of the lsb to start the conversion + * @return a hexadecimal digit representing the selected bits + * @throws IllegalArgumentException if {@code src} is empty + * @throws NullPointerException if {@code src} is {@code null} + */ + public static char binaryToHexDigit(final boolean[] src, final int srcPos) { + if (src.length == 0) { + throw new IllegalArgumentException("Cannot convert an empty array."); + } + if (src.length > srcPos + 3 && src[srcPos + 3]) { + if (src.length > srcPos + 2 && src[srcPos + 2]) { + if (src.length > srcPos + 1 && src[srcPos + 1]) { + return src[srcPos] ? 'f' : 'e'; + } + return src[srcPos] ? 'd' : 'c'; + } + if (src.length > srcPos + 1 && src[srcPos + 1]) { + return src[srcPos] ? 'b' : 'a'; + } + return src[srcPos] ? '9' : '8'; + } + if (src.length > srcPos + 2 && src[srcPos + 2]) { + if (src.length > srcPos + 1 && src[srcPos + 1]) { + return src[srcPos] ? '7' : '6'; + } + return src[srcPos] ? '5' : '4'; + } + if (src.length > srcPos + 1 && src[srcPos + 1]) { + return src[srcPos] ? '3' : '2'; + } + return src[srcPos] ? '1' : '0'; + } + + /** + *

              + * Converts binary (represented as boolean array) to a hexadecimal digit using the Msb0 bit + * ordering. + *

              + *

              + * (1, 0, 0, 0) is converted as follow: '8' + *

              + * + * @param src the binary to convert + * @return a hexadecimal digit representing the selected bits + * @throws IllegalArgumentException if {@code src} is empty, {@code src.length < 4} or + * {@code src.length > 8} + * @throws NullPointerException if {@code src} is {@code null} + */ + public static char binaryToHexDigitMsb0_4bits(final boolean[] src) { + return binaryToHexDigitMsb0_4bits(src, 0); + } + + /** + *

              + * Converts binary (represented as boolean array) to a hexadecimal digit using the Msb0 bit + * ordering. + *

              + *

              + * (1, 0, 0, 0) is converted as follow: '8' (1,0,0,1,1,0,1,0) with srcPos = 3 is converted + * to 'D' + *

              + * + * @param src the binary to convert + * @param srcPos the position of the lsb to start the conversion + * @return a hexadecimal digit representing the selected bits + * @throws IllegalArgumentException if {@code src} is empty, {@code src.length > 8} or + * {@code src.length - srcPos < 4} + * @throws NullPointerException if {@code src} is {@code null} + */ + public static char binaryToHexDigitMsb0_4bits(final boolean[] src, final int srcPos) { + if (src.length > 8) { + throw new IllegalArgumentException("src.length>8: src.length=" + src.length); + } + if (src.length - srcPos < 4) { + throw new IllegalArgumentException("src.length-srcPos<4: src.length=" + src.length + ", srcPos=" + srcPos); + } + if (src[srcPos + 3]) { + if (src[srcPos + 2]) { + if (src[srcPos + 1]) { + return src[srcPos] ? 'f' : '7'; + } + return src[srcPos] ? 'b' : '3'; + } + if (src[srcPos + 1]) { + return src[srcPos] ? 'd' : '5'; + } + return src[srcPos] ? '9' : '1'; + } + if (src[srcPos + 2]) { + if (src[srcPos + 1]) { + return src[srcPos] ? 'e' : '6'; + } + return src[srcPos] ? 'a' : '2'; + } + if (src[srcPos + 1]) { + return src[srcPos] ? 'c' : '4'; + } + return src[srcPos] ? '8' : '0'; + } + + /** + *

              + * Converts the first 4 bits of a binary (represented as boolean array) in big endian Msb0 + * bit ordering to a hexadecimal digit. + *

              + *

              + * (1, 0, 0, 0) is converted as follow: '8' (1,0,0,0,0,0,0,0, 0,0,0,0,0,1,0,0) is converted + * to '4' + *

              + * + * @param src the binary to convert + * @return a hexadecimal digit representing the selected bits + * @throws IllegalArgumentException if {@code src} is empty + * @throws NullPointerException if {@code src} is {@code null} + */ + public static char binaryBeMsb0ToHexDigit(final boolean[] src) { + return binaryBeMsb0ToHexDigit(src, 0); + } + + /** + *

              + * Converts a binary (represented as boolean array) in big endian Msb0 bit ordering to a + * hexadecimal digit. + *

              + *

              + * (1, 0, 0, 0) with srcPos = 0 is converted as follow: '8' (1,0,0,0,0,0,0,0, + * 0,0,0,1,0,1,0,0) with srcPos = 2 is converted to '5' + *

              + * + * @param src the binary to convert + * @param srcPos the position of the lsb to start the conversion + * @return a hexadecimal digit representing the selected bits + * @throws IllegalArgumentException if {@code src} is empty + * @throws NullPointerException if {@code src} is {@code null} + */ + public static char binaryBeMsb0ToHexDigit(boolean[] src, int srcPos) { + if (src.length == 0) { + throw new IllegalArgumentException("Cannot convert an empty array."); + } + final int beSrcPos = src.length - 1 - srcPos; + final int srcLen = Math.min(4, beSrcPos + 1); + final boolean[] paddedSrc = new boolean[4]; + System.arraycopy(src, beSrcPos + 1 - srcLen, paddedSrc, 4 - srcLen, srcLen); + src = paddedSrc; + srcPos = 0; + if (src[srcPos]) { + if (src.length > srcPos + 1 && src[srcPos + 1]) { + if (src.length > srcPos + 2 && src[srcPos + 2]) { + return src.length > srcPos + 3 && src[srcPos + 3] ? 'f' : 'e'; + } + return src.length > srcPos + 3 && src[srcPos + 3] ? 'd' : 'c'; + } + if (src.length > srcPos + 2 && src[srcPos + 2]) { + return src.length > srcPos + 3 && src[srcPos + 3] ? 'b' : 'a'; + } + return src.length > srcPos + 3 && src[srcPos + 3] ? '9' : '8'; + } + if (src.length > srcPos + 1 && src[srcPos + 1]) { + if (src.length > srcPos + 2 && src[srcPos + 2]) { + return src.length > srcPos + 3 && src[srcPos + 3] ? '7' : '6'; + } + return src.length > srcPos + 3 && src[srcPos + 3] ? '5' : '4'; + } + if (src.length > srcPos + 2 && src[srcPos + 2]) { + return src.length > srcPos + 3 && src[srcPos + 3] ? '3' : '2'; + } + return src.length > srcPos + 3 && src[srcPos + 3] ? '1' : '0'; + } + + /** + *

              + * Converts the 4 lsb of an int to a hexadecimal digit. + *

              + *

              + * 0 returns '0' + *

              + *

              + * 1 returns '1' + *

              + *

              + * 10 returns 'A' and so on... + *

              + * + * @param nibble the 4 bits to convert + * @return a hexadecimal digit representing the 4 lsb of {@code nibble} + * @throws IllegalArgumentException if {@code nibble < 0} or {@code nibble > 15} + */ + public static char intToHexDigit(final int nibble) { + final char c = Character.forDigit(nibble, 16); + if (c == Character.MIN_VALUE) { + throw new IllegalArgumentException("nibble value not between 0 and 15: " + nibble); + } + return c; + } + + /** + *

              + * Converts the 4 lsb of an int to a hexadecimal digit encoded using the Msb0 bit ordering. + *

              + *

              + * 0 returns '0' + *

              + *

              + * 1 returns '8' + *

              + *

              + * 10 returns '5' and so on... + *

              + * + * @param nibble the 4 bits to convert + * @return a hexadecimal digit representing the 4 lsb of {@code nibble} + * @throws IllegalArgumentException if {@code nibble < 0} or {@code nibble > 15} + */ + public static char intToHexDigitMsb0(final int nibble) { + switch (nibble) { + case 0x0: + return '0'; + case 0x1: + return '8'; + case 0x2: + return '4'; + case 0x3: + return 'c'; + case 0x4: + return '2'; + case 0x5: + return 'a'; + case 0x6: + return '6'; + case 0x7: + return 'e'; + case 0x8: + return '1'; + case 0x9: + return '9'; + case 0xA: + return '5'; + case 0xB: + return 'd'; + case 0xC: + return '3'; + case 0xD: + return 'b'; + case 0xE: + return '7'; + case 0xF: + return 'f'; + default: + throw new IllegalArgumentException("nibble value not between 0 and 15: " + nibble); + } + } + + /** + *

              + * Converts an array of int into a long using the default (little endian, Lsb0) byte and bit + * ordering. + *

              + * + * @param src the int array to convert + * @param srcPos the position in {@code src}, in int unit, from where to start the + * conversion + * @param dstInit initial value of the destination long + * @param dstPos the position of the lsb, in bits, in the result long + * @param nInts the number of ints to convert + * @return a long containing the selected bits + * @throws IllegalArgumentException if {@code (nInts-1)*32+dstPos >= 64} + * @throws NullPointerException if {@code src} is {@code null} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nInts > src.length} + */ + public static long intArrayToLong(final int[] src, final int srcPos, final long dstInit, final int dstPos, + final int nInts) { + if ((src.length == 0 && srcPos == 0) || 0 == nInts) { + return dstInit; + } + if ((nInts - 1) * 32 + dstPos >= 64) { + throw new IllegalArgumentException("(nInts-1)*32+dstPos is greather or equal to than 64"); + } + long out = dstInit; + int shift = 0; + for (int i = 0; i < nInts; i++) { + shift = i * 32 + dstPos; + final long bits = ((0xffffffffL & src[i + srcPos]) << shift); + final long mask = 0xffffffffL << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + *

              + * Converts an array of short into a long using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the short array to convert + * @param srcPos the position in {@code src}, in short unit, from where to start the + * conversion + * @param dstInit initial value of the destination long + * @param dstPos the position of the lsb, in bits, in the result long + * @param nShorts the number of shorts to convert + * @return a long containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code (nShorts-1)*16+dstPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nShorts > src.length} + */ + public static long shortArrayToLong(final short[] src, final int srcPos, final long dstInit, final int dstPos, + final int nShorts) { + if ((src.length == 0 && srcPos == 0) || 0 == nShorts) { + return dstInit; + } + if ((nShorts - 1) * 16 + dstPos >= 64) { + throw new IllegalArgumentException("(nShorts-1)*16+dstPos is greather or equal to than 64"); + } + long out = dstInit; + int shift = 0; + for (int i = 0; i < nShorts; i++) { + shift = i * 16 + dstPos; + final long bits = (0xffffL & src[i + srcPos]) << shift; + final long mask = 0xffffL << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + *

              + * Converts an array of short into a int using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the short array to convert + * @param srcPos the position in {@code src}, in short unit, from where to start the + * conversion + * @param dstInit initial value of the destination int + * @param dstPos the position of the lsb, in bits, in the result int + * @param nShorts the number of shorts to convert + * @return a int containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code (nShorts-1)*16+dstPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nShorts > src.length} + */ + public static int shortArrayToInt(final short[] src, final int srcPos, final int dstInit, final int dstPos, + final int nShorts) { + if ((src.length == 0 && srcPos == 0) || 0 == nShorts) { + return dstInit; + } + if ((nShorts - 1) * 16 + dstPos >= 32) { + throw new IllegalArgumentException("(nShorts-1)*16+dstPos is greather or equal to than 32"); + } + int out = dstInit; + int shift = 0; + for (int i = 0; i < nShorts; i++) { + shift = i * 16 + dstPos; + final int bits = (0xffff & src[i + srcPos]) << shift; + final int mask = 0xffff << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + *

              + * Converts an array of byte into a long using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the byte array to convert + * @param srcPos the position in {@code src}, in byte unit, from where to start the + * conversion + * @param dstInit initial value of the destination long + * @param dstPos the position of the lsb, in bits, in the result long + * @param nBytes the number of bytes to convert + * @return a long containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+dstPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBytes > src.length} + */ + public static long byteArrayToLong(final byte[] src, final int srcPos, final long dstInit, final int dstPos, + final int nBytes) { + if ((src.length == 0 && srcPos == 0) || 0 == nBytes) { + return dstInit; + } + if ((nBytes - 1) * 8 + dstPos >= 64) { + throw new IllegalArgumentException("(nBytes-1)*8+dstPos is greather or equal to than 64"); + } + long out = dstInit; + int shift = 0; + for (int i = 0; i < nBytes; i++) { + shift = i * 8 + dstPos; + final long bits = (0xffL & src[i + srcPos]) << shift; + final long mask = 0xffL << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + *

              + * Converts an array of byte into a int using the default (little endian, Lsb0) byte and bit + * ordering. + *

              + * + * @param src the byte array to convert + * @param srcPos the position in {@code src}, in byte unit, from where to start the + * conversion + * @param dstInit initial value of the destination int + * @param dstPos the position of the lsb, in bits, in the result int + * @param nBytes the number of bytes to convert + * @return a int containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+dstPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBytes > src.length} + */ + public static int byteArrayToInt(final byte[] src, final int srcPos, final int dstInit, final int dstPos, + final int nBytes) { + if ((src.length == 0 && srcPos == 0) || 0 == nBytes) { + return dstInit; + } + if ((nBytes - 1) * 8 + dstPos >= 32) { + throw new IllegalArgumentException("(nBytes-1)*8+dstPos is greather or equal to than 32"); + } + int out = dstInit; + int shift = 0; + for (int i = 0; i < nBytes; i++) { + shift = i * 8 + dstPos; + final int bits = (0xff & src[i + srcPos]) << shift; + final int mask = 0xff << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + *

              + * Converts an array of byte into a short using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the byte array to convert + * @param srcPos the position in {@code src}, in byte unit, from where to start the + * conversion + * @param dstInit initial value of the destination short + * @param dstPos the position of the lsb, in bits, in the result short + * @param nBytes the number of bytes to convert + * @return a short containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+dstPos >= 16} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBytes > src.length} + */ + public static short byteArrayToShort(final byte[] src, final int srcPos, final short dstInit, final int dstPos, + final int nBytes) { + if ((src.length == 0 && srcPos == 0) || 0 == nBytes) { + return dstInit; + } + if ((nBytes - 1) * 8 + dstPos >= 16) { + throw new IllegalArgumentException("(nBytes-1)*8+dstPos is greather or equal to than 16"); + } + short out = dstInit; + int shift = 0; + for (int i = 0; i < nBytes; i++) { + shift = i * 8 + dstPos; + final int bits = (0xff & src[i + srcPos]) << shift; + final int mask = 0xff << shift; + out = (short) ((out & ~mask) | bits); + } + return out; + } + + /** + *

              + * Converts an array of Char into a long using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the hex string to convert + * @param srcPos the position in {@code src}, in Char unit, from where to start the + * conversion + * @param dstInit initial value of the destination long + * @param dstPos the position of the lsb, in bits, in the result long + * @param nHex the number of Chars to convert + * @return a long containing the selected bits + * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 64} + */ + public static long hexToLong(final String src, final int srcPos, final long dstInit, final int dstPos, + final int nHex) { + if (0 == nHex) { + return dstInit; + } + if ((nHex - 1) * 4 + dstPos >= 64) { + throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greather or equal to than 64"); + } + long out = dstInit; + int shift = 0; + for (int i = 0; i < nHex; i++) { + shift = i * 4 + dstPos; + final long bits = (0xfL & hexDigitToInt(src.charAt(i + srcPos))) << shift; + final long mask = 0xfL << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + *

              + * Converts an array of Char into a int using the default (little endian, Lsb0) byte and bit + * ordering. + *

              + * + * @param src the hex string to convert + * @param srcPos the position in {@code src}, in Char unit, from where to start the + * conversion + * @param dstInit initial value of the destination int + * @param dstPos the position of the lsb, in bits, in the result int + * @param nHex the number of Chars to convert + * @return a int containing the selected bits + * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 32} + */ + public static int hexToInt(final String src, final int srcPos, final int dstInit, final int dstPos, final int nHex) { + if (0 == nHex) { + return dstInit; + } + if ((nHex - 1) * 4 + dstPos >= 32) { + throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greather or equal to than 32"); + } + int out = dstInit; + int shift = 0; + for (int i = 0; i < nHex; i++) { + shift = i * 4 + dstPos; + final int bits = (0xf & hexDigitToInt(src.charAt(i + srcPos))) << shift; + final int mask = 0xf << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + *

              + * Converts an array of Char into a short using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the hex string to convert + * @param srcPos the position in {@code src}, in Char unit, from where to start the + * conversion + * @param dstInit initial value of the destination short + * @param dstPos the position of the lsb, in bits, in the result short + * @param nHex the number of Chars to convert + * @return a short containing the selected bits + * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 16} + */ + public static short hexToShort(final String src, final int srcPos, final short dstInit, final int dstPos, + final int nHex) { + if (0 == nHex) { + return dstInit; + } + if ((nHex - 1) * 4 + dstPos >= 16) { + throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greather or equal to than 16"); + } + short out = dstInit; + int shift = 0; + for (int i = 0; i < nHex; i++) { + shift = i * 4 + dstPos; + final int bits = (0xf & hexDigitToInt(src.charAt(i + srcPos))) << shift; + final int mask = 0xf << shift; + out = (short) ((out & ~mask) | bits); + } + return out; + } + + /** + *

              + * Converts an array of Char into a byte using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the hex string to convert + * @param srcPos the position in {@code src}, in Char unit, from where to start the + * conversion + * @param dstInit initial value of the destination byte + * @param dstPos the position of the lsb, in bits, in the result byte + * @param nHex the number of Chars to convert + * @return a byte containing the selected bits + * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 8} + */ + public static byte hexToByte(final String src, final int srcPos, final byte dstInit, final int dstPos, + final int nHex) { + if (0 == nHex) { + return dstInit; + } + if ((nHex - 1) * 4 + dstPos >= 8) { + throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greather or equal to than 8"); + } + byte out = dstInit; + int shift = 0; + for (int i = 0; i < nHex; i++) { + shift = i * 4 + dstPos; + final int bits = (0xf & hexDigitToInt(src.charAt(i + srcPos))) << shift; + final int mask = 0xf << shift; + out = (byte) ((out & ~mask) | bits); + } + return out; + } + + /** + *

              + * Converts binary (represented as boolean array) into a long using the default (little + * endian, Lsb0) byte and bit ordering. + *

              + * + * @param src the binary to convert + * @param srcPos the position in {@code src}, in boolean unit, from where to start the + * conversion + * @param dstInit initial value of the destination long + * @param dstPos the position of the lsb, in bits, in the result long + * @param nBools the number of booleans to convert + * @return a long containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} + */ + public static long binaryToLong(final boolean[] src, final int srcPos, final long dstInit, final int dstPos, + final int nBools) { + if ((src.length == 0 && srcPos == 0) || 0 == nBools) { + return dstInit; + } + if (nBools - 1 + dstPos >= 64) { + throw new IllegalArgumentException("nBools-1+dstPos is greather or equal to than 64"); + } + long out = dstInit; + int shift = 0; + for (int i = 0; i < nBools; i++) { + shift = i + dstPos; + final long bits = (src[i + srcPos] ? 1L : 0) << shift; + final long mask = 0x1L << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + *

              + * Converts binary (represented as boolean array) into a int using the default (little + * endian, Lsb0) byte and bit ordering. + *

              + * + * @param src the binary to convert + * @param srcPos the position in {@code src}, in boolean unit, from where to start the + * conversion + * @param dstInit initial value of the destination int + * @param dstPos the position of the lsb, in bits, in the result int + * @param nBools the number of booleans to convert + * @return a int containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} + */ + public static int binaryToInt(final boolean[] src, final int srcPos, final int dstInit, final int dstPos, + final int nBools) { + if ((src.length == 0 && srcPos == 0) || 0 == nBools) { + return dstInit; + } + if (nBools - 1 + dstPos >= 32) { + throw new IllegalArgumentException("nBools-1+dstPos is greather or equal to than 32"); + } + int out = dstInit; + int shift = 0; + for (int i = 0; i < nBools; i++) { + shift = i + dstPos; + final int bits = (src[i + srcPos] ? 1 : 0) << shift; + final int mask = 0x1 << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + *

              + * Converts binary (represented as boolean array) into a short using the default (little + * endian, Lsb0) byte and bit ordering. + *

              + * + * @param src the binary to convert + * @param srcPos the position in {@code src}, in boolean unit, from where to start the + * conversion + * @param dstInit initial value of the destination short + * @param dstPos the position of the lsb, in bits, in the result short + * @param nBools the number of booleans to convert + * @return a short containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 16} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} + */ + public static short binaryToShort(final boolean[] src, final int srcPos, final short dstInit, final int dstPos, + final int nBools) { + if ((src.length == 0 && srcPos == 0) || 0 == nBools) { + return dstInit; + } + if (nBools - 1 + dstPos >= 16) { + throw new IllegalArgumentException("nBools-1+dstPos is greather or equal to than 16"); + } + short out = dstInit; + int shift = 0; + for (int i = 0; i < nBools; i++) { + shift = i + dstPos; + final int bits = (src[i + srcPos] ? 1 : 0) << shift; + final int mask = 0x1 << shift; + out = (short) ((out & ~mask) | bits); + } + return out; + } + + /** + *

              + * Converts binary (represented as boolean array) into a byte using the default (little + * endian, Lsb0) byte and bit ordering. + *

              + * + * @param src the binary to convert + * @param srcPos the position in {@code src}, in boolean unit, from where to start the + * conversion + * @param dstInit initial value of the destination byte + * @param dstPos the position of the lsb, in bits, in the result byte + * @param nBools the number of booleans to convert + * @return a byte containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 8} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} + */ + public static byte binaryToByte(final boolean[] src, final int srcPos, final byte dstInit, final int dstPos, + final int nBools) { + if ((src.length == 0 && srcPos == 0) || 0 == nBools) { + return dstInit; + } + if (nBools - 1 + dstPos >= 8) { + throw new IllegalArgumentException("nBools-1+dstPos is greather or equal to than 8"); + } + byte out = dstInit; + int shift = 0; + for (int i = 0; i < nBools; i++) { + shift = i + dstPos; + final int bits = (src[i + srcPos] ? 1 : 0) << shift; + final int mask = 0x1 << shift; + out = (byte) ((out & ~mask) | bits); + } + return out; + } + + /** + *

              + * Converts a long into an array of int using the default (little endian, Lsb0) byte and bit + * ordering. + *

              + * + * @param src the long to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nInts the number of ints to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} and {@code nInts > 0} + * @throws IllegalArgumentException if {@code (nInts-1)*32+srcPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nInts > dst.length} + */ + public static int[] longToIntArray(final long src, final int srcPos, final int[] dst, final int dstPos, + final int nInts) { + if (0 == nInts) { + return dst; + } + if ((nInts - 1) * 32 + srcPos >= 64) { + throw new IllegalArgumentException("(nInts-1)*32+srcPos is greather or equal to than 64"); + } + int shift = 0; + for (int i = 0; i < nInts; i++) { + shift = i * 32 + srcPos; + dst[dstPos + i] = (int) (0xffffffff & (src >> shift)); + } + return dst; + } + + /** + *

              + * Converts a long into an array of short using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the long to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nShorts the number of shorts to copy to {@code dst}, must be smaller or equal to + * the width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code (nShorts-1)*16+srcPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nShorts > dst.length} + */ + public static short[] longToShortArray(final long src, final int srcPos, final short[] dst, final int dstPos, + final int nShorts) { + if (0 == nShorts) { + return dst; + } + if ((nShorts - 1) * 16 + srcPos >= 64) { + throw new IllegalArgumentException("(nShorts-1)*16+srcPos is greather or equal to than 64"); + } + int shift = 0; + for (int i = 0; i < nShorts; i++) { + shift = i * 16 + srcPos; + dst[dstPos + i] = (short) (0xffff & (src >> shift)); + } + return dst; + } + + /** + *

              + * Converts a int into an array of short using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the int to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nShorts the number of shorts to copy to {@code dst}, must be smaller or equal to + * the width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code (nShorts-1)*16+srcPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nShorts > dst.length} + */ + public static short[] intToShortArray(final int src, final int srcPos, final short[] dst, final int dstPos, + final int nShorts) { + if (0 == nShorts) { + return dst; + } + if ((nShorts - 1) * 16 + srcPos >= 32) { + throw new IllegalArgumentException("(nShorts-1)*16+srcPos is greather or equal to than 32"); + } + int shift = 0; + for (int i = 0; i < nShorts; i++) { + shift = i * 16 + srcPos; + dst[dstPos + i] = (short) (0xffff & (src >> shift)); + } + return dst; + } + + /** + *

              + * Converts a long into an array of byte using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the long to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBytes the number of bytes to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+srcPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBytes > dst.length} + */ + public static byte[] longToByteArray(final long src, final int srcPos, final byte[] dst, final int dstPos, + final int nBytes) { + if (0 == nBytes) { + return dst; + } + if ((nBytes - 1) * 8 + srcPos >= 64) { + throw new IllegalArgumentException("(nBytes-1)*8+srcPos is greather or equal to than 64"); + } + int shift = 0; + for (int i = 0; i < nBytes; i++) { + shift = i * 8 + srcPos; + dst[dstPos + i] = (byte) (0xff & (src >> shift)); + } + return dst; + } + + /** + *

              + * Converts a int into an array of byte using the default (little endian, Lsb0) byte and bit + * ordering. + *

              + * + * @param src the int to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBytes the number of bytes to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+srcPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBytes > dst.length} + */ + public static byte[] intToByteArray(final int src, final int srcPos, final byte[] dst, final int dstPos, + final int nBytes) { + if (0 == nBytes) { + return dst; + } + if ((nBytes - 1) * 8 + srcPos >= 32) { + throw new IllegalArgumentException("(nBytes-1)*8+srcPos is greather or equal to than 32"); + } + int shift = 0; + for (int i = 0; i < nBytes; i++) { + shift = i * 8 + srcPos; + dst[dstPos + i] = (byte) (0xff & (src >> shift)); + } + return dst; + } + + /** + *

              + * Converts a short into an array of byte using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the short to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBytes the number of bytes to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+srcPos >= 16} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBytes > dst.length} + */ + public static byte[] shortToByteArray(final short src, final int srcPos, final byte[] dst, final int dstPos, + final int nBytes) { + if (0 == nBytes) { + return dst; + } + if ((nBytes - 1) * 8 + srcPos >= 16) { + throw new IllegalArgumentException("(nBytes-1)*8+srcPos is greather or equal to than 16"); + } + int shift = 0; + for (int i = 0; i < nBytes; i++) { + shift = i * 8 + srcPos; + dst[dstPos + i] = (byte) (0xff & (src >> shift)); + } + return dst; + } + + /** + *

              + * Converts a long into an array of Char using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the long to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dstInit the initial value for the result String + * @param dstPos the position in {@code dst} where to copy the result + * @param nHexs the number of Chars to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws IllegalArgumentException if {@code (nHexs-1)*4+srcPos >= 64} + * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos} + */ + public static String longToHex(final long src, final int srcPos, final String dstInit, final int dstPos, + final int nHexs) { + if (0 == nHexs) { + return dstInit; + } + if ((nHexs - 1) * 4 + srcPos >= 64) { + throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greather or equal to than 64"); + } + final StringBuilder sb = new StringBuilder(dstInit); + int shift = 0; + int append = sb.length(); + for (int i = 0; i < nHexs; i++) { + shift = i * 4 + srcPos; + final int bits = (int) (0xF & (src >> shift)); + if (dstPos + i == append) { + ++append; + sb.append(intToHexDigit(bits)); + } else { + sb.setCharAt(dstPos + i, intToHexDigit(bits)); + } + } + return sb.toString(); + } + + /** + *

              + * Converts a int into an array of Char using the default (little endian, Lsb0) byte and bit + * ordering. + *

              + * + * @param src the int to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dstInit the initial value for the result String + * @param dstPos the position in {@code dst} where to copy the result + * @param nHexs the number of Chars to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws IllegalArgumentException if {@code (nHexs-1)*4+srcPos >= 32} + * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos} + */ + public static String intToHex(final int src, final int srcPos, final String dstInit, final int dstPos, + final int nHexs) { + if (0 == nHexs) { + return dstInit; + } + if ((nHexs - 1) * 4 + srcPos >= 32) { + throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greather or equal to than 32"); + } + final StringBuilder sb = new StringBuilder(dstInit); + int shift = 0; + int append = sb.length(); + for (int i = 0; i < nHexs; i++) { + shift = i * 4 + srcPos; + final int bits = 0xF & (src >> shift); + if (dstPos + i == append) { + ++append; + sb.append(intToHexDigit(bits)); + } else { + sb.setCharAt(dstPos + i, intToHexDigit(bits)); + } + } + return sb.toString(); + } + + /** + *

              + * Converts a short into an array of Char using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the short to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dstInit the initial value for the result String + * @param dstPos the position in {@code dst} where to copy the result + * @param nHexs the number of Chars to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws IllegalArgumentException if {@code (nHexs-1)*4+srcPos >= 16} + * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos} + */ + public static String shortToHex(final short src, final int srcPos, final String dstInit, final int dstPos, + final int nHexs) { + if (0 == nHexs) { + return dstInit; + } + if ((nHexs - 1) * 4 + srcPos >= 16) { + throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greather or equal to than 16"); + } + final StringBuilder sb = new StringBuilder(dstInit); + int shift = 0; + int append = sb.length(); + for (int i = 0; i < nHexs; i++) { + shift = i * 4 + srcPos; + final int bits = 0xF & (src >> shift); + if (dstPos + i == append) { + ++append; + sb.append(intToHexDigit(bits)); + } else { + sb.setCharAt(dstPos + i, intToHexDigit(bits)); + } + } + return sb.toString(); + } + + /** + *

              + * Converts a byte into an array of Char using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the byte to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dstInit the initial value for the result String + * @param dstPos the position in {@code dst} where to copy the result + * @param nHexs the number of Chars to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws IllegalArgumentException if {@code (nHexs-1)*4+srcPos >= 8} + * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos} + */ + public static String byteToHex(final byte src, final int srcPos, final String dstInit, final int dstPos, + final int nHexs) { + if (0 == nHexs) { + return dstInit; + } + if ((nHexs - 1) * 4 + srcPos >= 8) { + throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greather or equal to than 8"); + } + final StringBuilder sb = new StringBuilder(dstInit); + int shift = 0; + int append = sb.length(); + for (int i = 0; i < nHexs; i++) { + shift = i * 4 + srcPos; + final int bits = 0xF & (src >> shift); + if (dstPos + i == append) { + ++append; + sb.append(intToHexDigit(bits)); + } else { + sb.setCharAt(dstPos + i, intToHexDigit(bits)); + } + } + return sb.toString(); + } + + /** + *

              + * Converts a long into an array of boolean using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the long to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to + * the width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} + */ + public static boolean[] longToBinary(final long src, final int srcPos, final boolean[] dst, final int dstPos, + final int nBools) { + if (0 == nBools) { + return dst; + } + if (nBools - 1 + srcPos >= 64) { + throw new IllegalArgumentException("nBools-1+srcPos is greather or equal to than 64"); + } + int shift = 0; + for (int i = 0; i < nBools; i++) { + shift = i + srcPos; + dst[dstPos + i] = ((0x1 & (src >> shift)) != 0); + } + return dst; + } + + /** + *

              + * Converts a int into an array of boolean using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the int to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to + * the width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} + */ + public static boolean[] intToBinary(final int src, final int srcPos, final boolean[] dst, final int dstPos, + final int nBools) { + if (0 == nBools) { + return dst; + } + if (nBools - 1 + srcPos >= 32) { + throw new IllegalArgumentException("nBools-1+srcPos is greather or equal to than 32"); + } + int shift = 0; + for (int i = 0; i < nBools; i++) { + shift = i + srcPos; + dst[dstPos + i] = ((0x1 & (src >> shift)) != 0); + } + return dst; + } + + /** + *

              + * Converts a short into an array of boolean using the default (little endian, Lsb0) byte + * and bit ordering. + *

              + * + * @param src the short to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to + * the width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 16} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} + */ + public static boolean[] shortToBinary(final short src, final int srcPos, final boolean[] dst, final int dstPos, + final int nBools) { + if (0 == nBools) { + return dst; + } + if (nBools - 1 + srcPos >= 16) { + throw new IllegalArgumentException("nBools-1+srcPos is greather or equal to than 16"); + } + int shift = 0; + assert ((nBools - 1) < 16 - srcPos); + for (int i = 0; i < nBools; i++) { + shift = i + srcPos; + dst[dstPos + i] = ((0x1 & (src >> shift)) != 0); + } + return dst; + } + + /** + *

              + * Converts a byte into an array of boolean using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the byte to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to + * the width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 8} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} + */ + public static boolean[] byteToBinary(final byte src, final int srcPos, final boolean[] dst, final int dstPos, + final int nBools) { + if (0 == nBools) { + return dst; + } + if (nBools - 1 + srcPos >= 8) { + throw new IllegalArgumentException("nBools-1+srcPos is greather or equal to than 8"); + } + int shift = 0; + for (int i = 0; i < nBools; i++) { + shift = i + srcPos; + dst[dstPos + i] = ((0x1 & (src >> shift)) != 0); + } + return dst; + } + + /** + *

              + * Converts UUID into an array of byte using the default (little endian, Lsb0) byte and bit + * ordering. + *

              + * + * @param src the UUID to convert + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBytes the number of bytes to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code nBytes > 16} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBytes > dst.length} + */ + public static byte[] uuidToByteArray(final UUID src, final byte[] dst, final int dstPos, final int nBytes) { + if (0 == nBytes) { + return dst; + } + if (nBytes > 16) { + throw new IllegalArgumentException("nBytes is greather than 16"); + } + longToByteArray(src.getMostSignificantBits(), 0, dst, dstPos, nBytes > 8 ? 8 : nBytes); + if (nBytes >= 8) { + longToByteArray(src.getLeastSignificantBits(), 0, dst, dstPos + 8, nBytes - 8); + } + return dst; + } + + /** + *

              + * Converts bytes from an array into a UUID using the default (little endian, Lsb0) byte and + * bit ordering. + *

              + * + * @param src the byte array to convert + * @param srcPos the position in {@code src} where to copy the result from + * @return a UUID + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if array does not contain at least 16 bytes beginning + * with {@code srcPos} + */ + public static UUID byteArrayToUuid(final byte[] src, final int srcPos) { + if (src.length - srcPos < 16) { + throw new IllegalArgumentException("Need at least 16 bytes for UUID"); + } + return new UUID(byteArrayToLong(src, srcPos, 0, 0, 8), byteArrayToLong(src, srcPos + 8, 0, 0, 8)); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/EnumUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/EnumUtils.java new file mode 100644 index 000000000..e4acc05cf --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/EnumUtils.java @@ -0,0 +1,311 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + *

              Utility library to provide helper methods for Java enums.

              + * + *

              #ThreadSafe#

              + * + * @since 3.0 + * @version $Id: EnumUtils.java 1606051 2014-06-27 12:22:17Z ggregory $ + */ +public class EnumUtils { + + private static final String NULL_ELEMENTS_NOT_PERMITTED = "null elements not permitted"; + private static final String CANNOT_STORE_S_S_VALUES_IN_S_BITS = "Cannot store %s %s values in %s bits"; + private static final String S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE = "%s does not seem to be an Enum type"; + private static final String ENUM_CLASS_MUST_BE_DEFINED = "EnumClass must be defined."; + + /** + * This constructor is public to permit tools that require a JavaBean + * instance to operate. + */ + public EnumUtils() { + } + + /** + *

              Gets the {@code Map} of enums by name.

              + * + *

              This method is useful when you need a map of enums by name.

              + * + * @param the type of the enumeration + * @param enumClass the class of the enum to query, not null + * @return the modifiable map of enum names to enums, never null + */ + public static > Map getEnumMap(final Class enumClass) { + final Map map = new LinkedHashMap(); + for (final E e: enumClass.getEnumConstants()) { + map.put(e.name(), e); + } + return map; + } + + /** + *

              Gets the {@code List} of enums.

              + * + *

              This method is useful when you need a list of enums rather than an array.

              + * + * @param the type of the enumeration + * @param enumClass the class of the enum to query, not null + * @return the modifiable list of enums, never null + */ + public static > List getEnumList(final Class enumClass) { + return new ArrayList(Arrays.asList(enumClass.getEnumConstants())); + } + + /** + *

              Checks if the specified name is a valid enum for the class.

              + * + *

              This method differs from {@link Enum#valueOf} in that checks if the name is + * a valid enum without needing to catch the exception.

              + * + * @param the type of the enumeration + * @param enumClass the class of the enum to query, not null + * @param enumName the enum name, null returns false + * @return true if the enum name is valid, otherwise false + */ + public static > boolean isValidEnum(final Class enumClass, final String enumName) { + if (enumName == null) { + return false; + } + try { + Enum.valueOf(enumClass, enumName); + return true; + } catch (final IllegalArgumentException ex) { + return false; + } + } + + /** + *

              Gets the enum for the class, returning {@code null} if not found.

              + * + *

              This method differs from {@link Enum#valueOf} in that it does not throw an exception + * for an invalid enum name.

              + * + * @param the type of the enumeration + * @param enumClass the class of the enum to query, not null + * @param enumName the enum name, null returns null + * @return the enum, null if not found + */ + public static > E getEnum(final Class enumClass, final String enumName) { + if (enumName == null) { + return null; + } + try { + return Enum.valueOf(enumClass, enumName); + } catch (final IllegalArgumentException ex) { + return null; + } + } + + /** + *

              Creates a long bit vector representation of the given subset of an Enum.

              + * + *

              This generates a value that is usable by {@link EnumUtils#processBitVector}.

              + * + *

              Do not use this method if you have more than 64 values in your Enum, as this + * would create a value greater than a long can hold.

              + * + * @param enumClass the class of the enum we are working with, not {@code null} + * @param values the values we want to convert, not {@code null}, neither containing {@code null} + * @param the type of the enumeration + * @return a long whose value provides a binary representation of the given set of enum values. + * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null} + * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values, + * or if any {@code values} {@code null} + * @since 3.0.1 + * @see #generateBitVectors(Class, Iterable) + */ + public static > long generateBitVector(final Class enumClass, final Iterable values) { + checkBitVectorable(enumClass); + Validate.notNull(values); + long total = 0; + for (final E constant : values) { + Validate.isTrue(constant != null, NULL_ELEMENTS_NOT_PERMITTED); + total |= 1 << constant.ordinal(); + } + return total; + } + + /** + *

              Creates a bit vector representation of the given subset of an Enum using as many {@code long}s as needed.

              + * + *

              This generates a value that is usable by {@link EnumUtils#processBitVectors}.

              + * + *

              Use this method if you have more than 64 values in your Enum.

              + * + * @param enumClass the class of the enum we are working with, not {@code null} + * @param values the values we want to convert, not {@code null}, neither containing {@code null} + * @param the type of the enumeration + * @return a long[] whose values provide a binary representation of the given set of enum values + * with least significant digits rightmost. + * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null} + * @throws IllegalArgumentException if {@code enumClass} is not an enum class, or if any {@code values} {@code null} + * @since 3.2 + */ + public static > long[] generateBitVectors(final Class enumClass, final Iterable values) { + asEnum(enumClass); + Validate.notNull(values); + final EnumSet condensed = EnumSet.noneOf(enumClass); + for (final E constant : values) { + Validate.isTrue(constant != null, NULL_ELEMENTS_NOT_PERMITTED); + condensed.add(constant); + } + final long[] result = new long[(enumClass.getEnumConstants().length - 1) / Long.SIZE + 1]; + for (final E value : condensed) { + result[value.ordinal() / Long.SIZE] |= 1 << (value.ordinal() % Long.SIZE); + } + ArrayUtils.reverse(result); + return result; + } + + /** + *

              Creates a long bit vector representation of the given array of Enum values.

              + * + *

              This generates a value that is usable by {@link EnumUtils#processBitVector}.

              + * + *

              Do not use this method if you have more than 64 values in your Enum, as this + * would create a value greater than a long can hold.

              + * + * @param enumClass the class of the enum we are working with, not {@code null} + * @param values the values we want to convert, not {@code null} + * @param the type of the enumeration + * @return a long whose value provides a binary representation of the given set of enum values. + * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null} + * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values + * @since 3.0.1 + * @see #generateBitVectors(Class, Iterable) + */ + public static > long generateBitVector(final Class enumClass, final E... values) { + Validate.noNullElements(values); + return generateBitVector(enumClass, Arrays. asList(values)); + } + + /** + *

              Creates a bit vector representation of the given subset of an Enum using as many {@code long}s as needed.

              + * + *

              This generates a value that is usable by {@link EnumUtils#processBitVectors}.

              + * + *

              Use this method if you have more than 64 values in your Enum.

              + * + * @param enumClass the class of the enum we are working with, not {@code null} + * @param values the values we want to convert, not {@code null}, neither containing {@code null} + * @param the type of the enumeration + * @return a long[] whose values provide a binary representation of the given set of enum values + * with least significant digits rightmost. + * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null} + * @throws IllegalArgumentException if {@code enumClass} is not an enum class, or if any {@code values} {@code null} + * @since 3.2 + */ + public static > long[] generateBitVectors(final Class enumClass, final E... values) { + asEnum(enumClass); + Validate.noNullElements(values); + final EnumSet condensed = EnumSet.noneOf(enumClass); + Collections.addAll(condensed, values); + final long[] result = new long[(enumClass.getEnumConstants().length - 1) / Long.SIZE + 1]; + for (final E value : condensed) { + result[value.ordinal() / Long.SIZE] |= 1 << (value.ordinal() % Long.SIZE); + } + ArrayUtils.reverse(result); + return result; + } + + /** + *

              Convert a long value created by {@link EnumUtils#generateBitVector} into the set of + * enum values that it represents.

              + * + *

              If you store this value, beware any changes to the enum that would affect ordinal values.

              + * @param enumClass the class of the enum we are working with, not {@code null} + * @param value the long value representation of a set of enum values + * @param the type of the enumeration + * @return a set of enum values + * @throws NullPointerException if {@code enumClass} is {@code null} + * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values + * @since 3.0.1 + */ + public static > EnumSet processBitVector(final Class enumClass, final long value) { + checkBitVectorable(enumClass).getEnumConstants(); + return processBitVectors(enumClass, value); + } + + /** + *

              Convert a {@code long[]} created by {@link EnumUtils#generateBitVectors} into the set of + * enum values that it represents.

              + * + *

              If you store this value, beware any changes to the enum that would affect ordinal values.

              + * @param enumClass the class of the enum we are working with, not {@code null} + * @param values the long[] bearing the representation of a set of enum values, least significant digits rightmost, not {@code null} + * @param the type of the enumeration + * @return a set of enum values + * @throws NullPointerException if {@code enumClass} is {@code null} + * @throws IllegalArgumentException if {@code enumClass} is not an enum class + * @since 3.2 + */ + public static > EnumSet processBitVectors(final Class enumClass, final long... values) { + final EnumSet results = EnumSet.noneOf(asEnum(enumClass)); + final long[] lvalues = ArrayUtils.clone(Validate.notNull(values)); + ArrayUtils.reverse(lvalues); + for (final E constant : enumClass.getEnumConstants()) { + final int block = constant.ordinal() / Long.SIZE; + if (block < lvalues.length && (lvalues[block] & 1 << (constant.ordinal() % Long.SIZE)) != 0) { + results.add(constant); + } + } + return results; + } + + /** + * Validate that {@code enumClass} is compatible with representation in a {@code long}. + * @param the type of the enumeration + * @param enumClass to check + * @return {@code enumClass} + * @throws NullPointerException if {@code enumClass} is {@code null} + * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values + * @since 3.0.1 + */ + private static > Class checkBitVectorable(final Class enumClass) { + final E[] constants = asEnum(enumClass).getEnumConstants(); + Validate.isTrue(constants.length <= Long.SIZE, CANNOT_STORE_S_S_VALUES_IN_S_BITS, + Integer.valueOf(constants.length), enumClass.getSimpleName(), Integer.valueOf(Long.SIZE)); + + return enumClass; + } + + /** + * Validate {@code enumClass}. + * @param the type of the enumeration + * @param enumClass to check + * @return {@code enumClass} + * @throws NullPointerException if {@code enumClass} is {@code null} + * @throws IllegalArgumentException if {@code enumClass} is not an enum class + * @since 3.2 + */ + private static > Class asEnum(final Class enumClass) { + Validate.notNull(enumClass, ENUM_CLASS_MUST_BE_DEFINED); + Validate.isTrue(enumClass.isEnum(), S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE, enumClass); + return enumClass; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/JavaVersion.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/JavaVersion.java new file mode 100644 index 000000000..b5e136a53 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/JavaVersion.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +/** + *

              An enum representing all the versions of the Java specification. + * This is intended to mirror available values from the + * java.specification.version System property.

              + * + * @since 3.0 + * @version $Id: JavaVersion.java 1654140 2015-01-23 08:41:18Z britter $ + */ +public enum JavaVersion { + + /** + * The Java version reported by Android. This is not an official Java version number. + */ + JAVA_0_9(1.5f, "0.9"), + + /** + * Java 1.1. + */ + JAVA_1_1(1.1f, "1.1"), + + /** + * Java 1.2. + */ + JAVA_1_2(1.2f, "1.2"), + + /** + * Java 1.3. + */ + JAVA_1_3(1.3f, "1.3"), + + /** + * Java 1.4. + */ + JAVA_1_4(1.4f, "1.4"), + + /** + * Java 1.5. + */ + JAVA_1_5(1.5f, "1.5"), + + /** + * Java 1.6. + */ + JAVA_1_6(1.6f, "1.6"), + + /** + * Java 1.7. + */ + JAVA_1_7(1.7f, "1.7"), + + /** + * Java 1.8. + */ + JAVA_1_8(1.8f, "1.8"), + + /** + * Java 1.9. + */ + JAVA_1_9(1.9f, "1.9"), + + /** + * Java 1.x, x > 9. Mainly introduced to avoid to break when a new version of Java is used. + */ + JAVA_RECENT(maxVersion(), Float.toString(maxVersion())); + + /** + * The float value. + */ + private final float value; + /** + * The standard name. + */ + private final String name; + + /** + * Constructor. + * + * @param value the float value + * @param name the standard name, not null + */ + JavaVersion(final float value, final String name) { + this.value = value; + this.name = name; + } + + //----------------------------------------------------------------------- + /** + *

              Whether this version of Java is at least the version of Java passed in.

              + * + *

              For example:
              + * {@code myVersion.atLeast(JavaVersion.JAVA_1_4)}

              + * + * @param requiredVersion the version to check against, not null + * @return true if this version is equal to or greater than the specified version + */ + public boolean atLeast(final JavaVersion requiredVersion) { + return this.value >= requiredVersion.value; + } + + /** + * Transforms the given string with a Java version number to the + * corresponding constant of this enumeration class. This method is used + * internally. + * + * @param nom the Java version as string + * @return the corresponding enumeration constant or null if the + * version is unknown + */ + // helper for static importing + static JavaVersion getJavaVersion(final String nom) { + return get(nom); + } + + /** + * Transforms the given string with a Java version number to the + * corresponding constant of this enumeration class. This method is used + * internally. + * + * @param nom the Java version as string + * @return the corresponding enumeration constant or null if the + * version is unknown + */ + static JavaVersion get(final String nom) { + if ("0.9".equals(nom)) { + return JAVA_0_9; + } else if ("1.1".equals(nom)) { + return JAVA_1_1; + } else if ("1.2".equals(nom)) { + return JAVA_1_2; + } else if ("1.3".equals(nom)) { + return JAVA_1_3; + } else if ("1.4".equals(nom)) { + return JAVA_1_4; + } else if ("1.5".equals(nom)) { + return JAVA_1_5; + } else if ("1.6".equals(nom)) { + return JAVA_1_6; + } else if ("1.7".equals(nom)) { + return JAVA_1_7; + } else if ("1.8".equals(nom)) { + return JAVA_1_8; + } else if ("1.9".equals(nom)) { + return JAVA_1_9; + } + if (nom == null) { + return null; + } + final float v = toFloatVersion(nom); + if ((v - 1.) < 1.) { // then we need to check decimals > .9 + final int firstComma = Math.max(nom.indexOf('.'), nom.indexOf(',')); + final int end = Math.max(nom.length(), nom.indexOf(',', firstComma)); + if (Float.parseFloat(nom.substring(firstComma + 1, end)) > .9f) { + return JAVA_RECENT; + } + } + return null; + } + + //----------------------------------------------------------------------- + /** + *

              The string value is overridden to return the standard name.

              + * + *

              For example, "1.5".

              + * + * @return the name, not null + */ + @Override + public String toString() { + return name; + } + + /** + * Gets the Java Version from the system or 2.0 if the {@code java.version} system property is not set. + * + * @return the value of {@code java.version} system property or 2.0 if it is not set. + */ + private static float maxVersion() { + final float v = toFloatVersion(System.getProperty("java.version", "2.0")); + if (v > 0) { + return v; + } + return 2f; + } + + /** + * Parses a float value from a String. + * + * @param value the String to parse. + * @return the float value represented by teh string or -1 if the given String can not be parsed. + */ + private static float toFloatVersion(final String value) { + final String[] toParse = value.split("\\."); + if (toParse.length >= 2) { + try { + return Float.parseFloat(toParse[0] + '.' + toParse[1]); + } catch (final NumberFormatException nfe) { + // no-op, let use default + } + } + return -1; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/LocaleUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/LocaleUtils.java new file mode 100644 index 000000000..ead722d91 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/LocaleUtils.java @@ -0,0 +1,328 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + *

              Operations to assist when working with a {@link Locale}.

              + * + *

              This class tries to handle {@code null} input gracefully. + * An exception will not be thrown for a {@code null} input. + * Each method documents its behaviour in more detail.

              + * + * @since 2.2 + * @version $Id: LocaleUtils.java 1606089 2014-06-27 13:18:55Z ggregory $ + */ +public class LocaleUtils { + + /** Concurrent map of language locales by country. */ + private static final ConcurrentMap> cLanguagesByCountry = + new ConcurrentHashMap>(); + + /** Concurrent map of country locales by language. */ + private static final ConcurrentMap> cCountriesByLanguage = + new ConcurrentHashMap>(); + + /** + *

              {@code LocaleUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as {@code LocaleUtils.toLocale("en_GB");}.

              + * + *

              This constructor is public to permit tools that require a JavaBean instance + * to operate.

              + */ + public LocaleUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + *

              Converts a String to a Locale.

              + * + *

              This method takes the string format of a locale and creates the + * locale object from it.

              + * + *
              +     *   LocaleUtils.toLocale("")           = new Locale("", "")
              +     *   LocaleUtils.toLocale("en")         = new Locale("en", "")
              +     *   LocaleUtils.toLocale("en_GB")      = new Locale("en", "GB")
              +     *   LocaleUtils.toLocale("en_GB_xxx")  = new Locale("en", "GB", "xxx")   (#)
              +     * 
              + * + *

              (#) The behaviour of the JDK variant constructor changed between JDK1.3 and JDK1.4. + * In JDK1.3, the constructor upper cases the variant, in JDK1.4, it doesn't. + * Thus, the result from getVariant() may vary depending on your JDK.

              + * + *

              This method validates the input strictly. + * The language code must be lowercase. + * The country code must be uppercase. + * The separator must be an underscore. + * The length must be correct. + *

              + * + * @param str the locale String to convert, null returns null + * @return a Locale, null if null input + * @throws IllegalArgumentException if the string is an invalid format + * @see Locale#forLanguageTag(String) + */ + public static Locale toLocale(final String str) { + if (str == null) { + return null; + } + if (str.isEmpty()) { // LANG-941 - JDK 8 introduced an empty locale where all fields are blank + return new Locale("", ""); + } + if (str.contains("#")) { // LANG-879 - Cannot handle Java 7 script & extensions + throw new IllegalArgumentException("Invalid locale format: " + str); + } + final int len = str.length(); + if (len < 2) { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + final char ch0 = str.charAt(0); + if (ch0 == '_') { + if (len < 3) { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + final char ch1 = str.charAt(1); + final char ch2 = str.charAt(2); + if (!Character.isUpperCase(ch1) || !Character.isUpperCase(ch2)) { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + if (len == 3) { + return new Locale("", str.substring(1, 3)); + } + if (len < 5) { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + if (str.charAt(3) != '_') { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + return new Locale("", str.substring(1, 3), str.substring(4)); + } + + final String[] split = str.split("_", -1); + final int occurrences = split.length -1; + switch (occurrences) { + case 0: + if (StringUtils.isAllLowerCase(str) && (len == 2 || len == 3)) { + return new Locale(str); + } + throw new IllegalArgumentException("Invalid locale format: " + str); + + case 1: + if (StringUtils.isAllLowerCase(split[0]) && + (split[0].length() == 2 || split[0].length() == 3) && + split[1].length() == 2 && StringUtils.isAllUpperCase(split[1])) { + return new Locale(split[0], split[1]); + } + throw new IllegalArgumentException("Invalid locale format: " + str); + + case 2: + if (StringUtils.isAllLowerCase(split[0]) && + (split[0].length() == 2 || split[0].length() == 3) && + (split[1].length() == 0 || (split[1].length() == 2 && StringUtils.isAllUpperCase(split[1]))) && + split[2].length() > 0) { + return new Locale(split[0], split[1], split[2]); + } + + //$FALL-THROUGH$ + default: + throw new IllegalArgumentException("Invalid locale format: " + str); + } + } + + //----------------------------------------------------------------------- + /** + *

              Obtains the list of locales to search through when performing + * a locale search.

              + * + *
              +     * localeLookupList(Locale("fr","CA","xxx"))
              +     *   = [Locale("fr","CA","xxx"), Locale("fr","CA"), Locale("fr")]
              +     * 
              + * + * @param locale the locale to start from + * @return the unmodifiable list of Locale objects, 0 being locale, not null + */ + public static List localeLookupList(final Locale locale) { + return localeLookupList(locale, locale); + } + + //----------------------------------------------------------------------- + /** + *

              Obtains the list of locales to search through when performing + * a locale search.

              + * + *
              +     * localeLookupList(Locale("fr", "CA", "xxx"), Locale("en"))
              +     *   = [Locale("fr","CA","xxx"), Locale("fr","CA"), Locale("fr"), Locale("en"]
              +     * 
              + * + *

              The result list begins with the most specific locale, then the + * next more general and so on, finishing with the default locale. + * The list will never contain the same locale twice.

              + * + * @param locale the locale to start from, null returns empty list + * @param defaultLocale the default locale to use if no other is found + * @return the unmodifiable list of Locale objects, 0 being locale, not null + */ + public static List localeLookupList(final Locale locale, final Locale defaultLocale) { + final List list = new ArrayList(4); + if (locale != null) { + list.add(locale); + if (locale.getVariant().length() > 0) { + list.add(new Locale(locale.getLanguage(), locale.getCountry())); + } + if (locale.getCountry().length() > 0) { + list.add(new Locale(locale.getLanguage(), "")); + } + if (list.contains(defaultLocale) == false) { + list.add(defaultLocale); + } + } + return Collections.unmodifiableList(list); + } + + //----------------------------------------------------------------------- + /** + *

              Obtains an unmodifiable list of installed locales.

              + * + *

              This method is a wrapper around {@link Locale#getAvailableLocales()}. + * It is more efficient, as the JDK method must create a new array each + * time it is called.

              + * + * @return the unmodifiable list of available locales + */ + public static List availableLocaleList() { + return SyncAvoid.AVAILABLE_LOCALE_LIST; + } + + //----------------------------------------------------------------------- + /** + *

              Obtains an unmodifiable set of installed locales.

              + * + *

              This method is a wrapper around {@link Locale#getAvailableLocales()}. + * It is more efficient, as the JDK method must create a new array each + * time it is called.

              + * + * @return the unmodifiable set of available locales + */ + public static Set availableLocaleSet() { + return SyncAvoid.AVAILABLE_LOCALE_SET; + } + + //----------------------------------------------------------------------- + /** + *

              Checks if the locale specified is in the list of available locales.

              + * + * @param locale the Locale object to check if it is available + * @return true if the locale is a known locale + */ + public static boolean isAvailableLocale(final Locale locale) { + return availableLocaleList().contains(locale); + } + + //----------------------------------------------------------------------- + /** + *

              Obtains the list of languages supported for a given country.

              + * + *

              This method takes a country code and searches to find the + * languages available for that country. Variant locales are removed.

              + * + * @param countryCode the 2 letter country code, null returns empty + * @return an unmodifiable List of Locale objects, not null + */ + public static List languagesByCountry(final String countryCode) { + if (countryCode == null) { + return Collections.emptyList(); + } + List langs = cLanguagesByCountry.get(countryCode); + if (langs == null) { + langs = new ArrayList(); + final List locales = availableLocaleList(); + for (int i = 0; i < locales.size(); i++) { + final Locale locale = locales.get(i); + if (countryCode.equals(locale.getCountry()) && + locale.getVariant().isEmpty()) { + langs.add(locale); + } + } + langs = Collections.unmodifiableList(langs); + cLanguagesByCountry.putIfAbsent(countryCode, langs); + langs = cLanguagesByCountry.get(countryCode); + } + return langs; + } + + //----------------------------------------------------------------------- + /** + *

              Obtains the list of countries supported for a given language.

              + * + *

              This method takes a language code and searches to find the + * countries available for that language. Variant locales are removed.

              + * + * @param languageCode the 2 letter language code, null returns empty + * @return an unmodifiable List of Locale objects, not null + */ + public static List countriesByLanguage(final String languageCode) { + if (languageCode == null) { + return Collections.emptyList(); + } + List countries = cCountriesByLanguage.get(languageCode); + if (countries == null) { + countries = new ArrayList(); + final List locales = availableLocaleList(); + for (int i = 0; i < locales.size(); i++) { + final Locale locale = locales.get(i); + if (languageCode.equals(locale.getLanguage()) && + locale.getCountry().length() != 0 && + locale.getVariant().isEmpty()) { + countries.add(locale); + } + } + countries = Collections.unmodifiableList(countries); + cCountriesByLanguage.putIfAbsent(languageCode, countries); + countries = cCountriesByLanguage.get(languageCode); + } + return countries; + } + + //----------------------------------------------------------------------- + // class to avoid synchronization (Init on demand) + static class SyncAvoid { + /** Unmodifiable list of available locales. */ + private static final List AVAILABLE_LOCALE_LIST; + /** Unmodifiable set of available locales. */ + private static final Set AVAILABLE_LOCALE_SET; + + static { + final List list = new ArrayList(Arrays.asList(Locale.getAvailableLocales())); // extra safe + AVAILABLE_LOCALE_LIST = Collections.unmodifiableList(list); + AVAILABLE_LOCALE_SET = Collections.unmodifiableSet(new HashSet(list)); + } + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/NotImplementedException.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/NotImplementedException.java new file mode 100644 index 000000000..9e8042a09 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/NotImplementedException.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +/** + *

              Thrown to indicate that a block of code has not been implemented. + * This exception supplements UnsupportedOperationException + * by providing a more semantically rich description of the problem.

              + * + *

              NotImplementedException represents the case where the + * author has yet to implement the logic at this point in the program. + * This can act as an exception based TODO tag.

              + * + *
              + * public void foo() {
              + *   try {
              + *     // do something that throws an Exception
              + *   } catch (Exception ex) {
              + *     // don't know what to do here yet
              + *     throw new NotImplementedException("TODO", ex);
              + *   }
              + * }
              + * 
              + * + * This class was originally added in Lang 2.0, but removed in 3.0. + * + * @since 3.2 + * @version $Id: NotImplementedException.java 905636 2010-02-02 14:03:32Z niallp $ + */ +public class NotImplementedException extends UnsupportedOperationException { + + private static final long serialVersionUID = 20131021L; + + private final String code; + + /** + * Constructs a NotImplementedException. + * + * @param message description of the exception + * @since 3.2 + */ + public NotImplementedException(final String message) { + this(message, (String) null); + } + + /** + * Constructs a NotImplementedException. + * + * @param cause cause of the exception + * @since 3.2 + */ + public NotImplementedException(final Throwable cause) { + this(cause, null); + } + + /** + * Constructs a NotImplementedException. + * + * @param message description of the exception + * @param cause cause of the exception + * @since 3.2 + */ + public NotImplementedException(final String message, final Throwable cause) { + this(message, cause, null); + } + + /** + * Constructs a NotImplementedException. + * + * @param message description of the exception + * @param code code indicating a resource for more information regarding the lack of implementation + * @since 3.2 + */ + public NotImplementedException(final String message, final String code) { + super(message); + this.code = code; + } + + /** + * Constructs a NotImplementedException. + * + * @param cause cause of the exception + * @param code code indicating a resource for more information regarding the lack of implementation + * @since 3.2 + */ + public NotImplementedException(final Throwable cause, final String code) { + super(cause); + this.code = code; + } + + /** + * Constructs a NotImplementedException. + * + * @param message description of the exception + * @param cause cause of the exception + * @param code code indicating a resource for more information regarding the lack of implementation + * @since 3.2 + */ + public NotImplementedException(final String message, final Throwable cause, final String code) { + super(message, cause); + this.code = code; + } + + /** + * Obtain the not implemented code. This is an unformatted piece of text intended to point to + * further information regarding the lack of implementation. It might, for example, be an issue + * tracker ID or a URL. + * + * @return a code indicating a resource for more information regarding the lack of implementation + */ + public String getCode() { + return this.code; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ObjectUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ObjectUtils.java new file mode 100644 index 000000000..f0766f307 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/ObjectUtils.java @@ -0,0 +1,951 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeSet; + +import com.fr.third.org.apache.commons.lang3.mutable.MutableInt; +import com.fr.third.org.apache.commons.lang3.text.StrBuilder; +import com.fr.third.org.apache.commons.lang3.exception.CloneFailedException; + +/** + *

              Operations on {@code Object}.

              + * + *

              This class tries to handle {@code null} input gracefully. + * An exception will generally not be thrown for a {@code null} input. + * Each method documents its behaviour in more detail.

              + * + *

              #ThreadSafe#

              + * @since 1.0 + * @version $Id: ObjectUtils.java 1669786 2015-03-28 15:09:29Z britter $ + */ +//@Immutable +public class ObjectUtils { + + /** + *

              Singleton used as a {@code null} placeholder where + * {@code null} has another meaning.

              + * + *

              For example, in a {@code HashMap} the + * {@link java.util.HashMap#get(java.lang.Object)} method returns + * {@code null} if the {@code Map} contains {@code null} or if there + * is no matching key. The {@code Null} placeholder can be used to + * distinguish between these two cases.

              + * + *

              Another example is {@code Hashtable}, where {@code null} + * cannot be stored.

              + * + *

              This instance is Serializable.

              + */ + public static final Null NULL = new Null(); + + /** + *

              {@code ObjectUtils} instances should NOT be constructed in + * standard programming. Instead, the static methods on the class should + * be used, such as {@code ObjectUtils.defaultIfNull("a","b");}.

              + * + *

              This constructor is public to permit tools that require a JavaBean + * instance to operate.

              + */ + public ObjectUtils() { + super(); + } + + // Defaulting + //----------------------------------------------------------------------- + /** + *

              Returns a default value if the object passed is {@code null}.

              + * + *
              +     * ObjectUtils.defaultIfNull(null, null)      = null
              +     * ObjectUtils.defaultIfNull(null, "")        = ""
              +     * ObjectUtils.defaultIfNull(null, "zz")      = "zz"
              +     * ObjectUtils.defaultIfNull("abc", *)        = "abc"
              +     * ObjectUtils.defaultIfNull(Boolean.TRUE, *) = Boolean.TRUE
              +     * 
              + * + * @param the type of the object + * @param object the {@code Object} to test, may be {@code null} + * @param defaultValue the default value to return, may be {@code null} + * @return {@code object} if it is not {@code null}, defaultValue otherwise + */ + public static T defaultIfNull(final T object, final T defaultValue) { + return object != null ? object : defaultValue; + } + + /** + *

              Returns the first value in the array which is not {@code null}. + * If all the values are {@code null} or the array is {@code null} + * or empty then {@code null} is returned.

              + * + *
              +     * ObjectUtils.firstNonNull(null, null)      = null
              +     * ObjectUtils.firstNonNull(null, "")        = ""
              +     * ObjectUtils.firstNonNull(null, null, "")  = ""
              +     * ObjectUtils.firstNonNull(null, "zz")      = "zz"
              +     * ObjectUtils.firstNonNull("abc", *)        = "abc"
              +     * ObjectUtils.firstNonNull(null, "xyz", *)  = "xyz"
              +     * ObjectUtils.firstNonNull(Boolean.TRUE, *) = Boolean.TRUE
              +     * ObjectUtils.firstNonNull()                = null
              +     * 
              + * + * @param the component type of the array + * @param values the values to test, may be {@code null} or empty + * @return the first value from {@code values} which is not {@code null}, + * or {@code null} if there are no non-null values + * @since 3.0 + */ + public static T firstNonNull(final T... values) { + if (values != null) { + for (final T val : values) { + if (val != null) { + return val; + } + } + } + return null; + } + + // Null-safe equals/hashCode + //----------------------------------------------------------------------- + /** + *

              Compares two objects for equality, where either one or both + * objects may be {@code null}.

              + * + *
              +     * ObjectUtils.equals(null, null)                  = true
              +     * ObjectUtils.equals(null, "")                    = false
              +     * ObjectUtils.equals("", null)                    = false
              +     * ObjectUtils.equals("", "")                      = true
              +     * ObjectUtils.equals(Boolean.TRUE, null)          = false
              +     * ObjectUtils.equals(Boolean.TRUE, "true")        = false
              +     * ObjectUtils.equals(Boolean.TRUE, Boolean.TRUE)  = true
              +     * ObjectUtils.equals(Boolean.TRUE, Boolean.FALSE) = false
              +     * 
              + * + * @param object1 the first object, may be {@code null} + * @param object2 the second object, may be {@code null} + * @return {@code true} if the values of both objects are the same + * @deprecated this method has been replaced by {@code java.util.Objects.equals(Object, Object)} in Java 7 and will + * be removed from future releases. + */ + @Deprecated + public static boolean equals(final Object object1, final Object object2) { + if (object1 == object2) { + return true; + } + if (object1 == null || object2 == null) { + return false; + } + return object1.equals(object2); + } + + /** + *

              Compares two objects for inequality, where either one or both + * objects may be {@code null}.

              + * + *
              +     * ObjectUtils.notEqual(null, null)                  = false
              +     * ObjectUtils.notEqual(null, "")                    = true
              +     * ObjectUtils.notEqual("", null)                    = true
              +     * ObjectUtils.notEqual("", "")                      = false
              +     * ObjectUtils.notEqual(Boolean.TRUE, null)          = true
              +     * ObjectUtils.notEqual(Boolean.TRUE, "true")        = true
              +     * ObjectUtils.notEqual(Boolean.TRUE, Boolean.TRUE)  = false
              +     * ObjectUtils.notEqual(Boolean.TRUE, Boolean.FALSE) = true
              +     * 
              + * + * @param object1 the first object, may be {@code null} + * @param object2 the second object, may be {@code null} + * @return {@code false} if the values of both objects are the same + */ + public static boolean notEqual(final Object object1, final Object object2) { + return ObjectUtils.equals(object1, object2) == false; + } + + /** + *

              Gets the hash code of an object returning zero when the + * object is {@code null}.

              + * + *
              +     * ObjectUtils.hashCode(null)   = 0
              +     * ObjectUtils.hashCode(obj)    = obj.hashCode()
              +     * 
              + * + * @param obj the object to obtain the hash code of, may be {@code null} + * @return the hash code of the object, or zero if null + * @since 2.1 + * @deprecated this method has been replaced by {@code java.util.Objects.hashCode(Object)} in Java 7 and will be + * removed in future releases + */ + @Deprecated + public static int hashCode(final Object obj) { + // hashCode(Object) retained for performance, as hash code is often critical + return obj == null ? 0 : obj.hashCode(); + } + + /** + *

              Gets the hash code for multiple objects.

              + * + *

              This allows a hash code to be rapidly calculated for a number of objects. + * The hash code for a single object is the not same as {@link #hashCode(Object)}. + * The hash code for multiple objects is the same as that calculated by an + * {@code ArrayList} containing the specified objects.

              + * + *
              +     * ObjectUtils.hashCodeMulti()                 = 1
              +     * ObjectUtils.hashCodeMulti((Object[]) null)  = 1
              +     * ObjectUtils.hashCodeMulti(a)                = 31 + a.hashCode()
              +     * ObjectUtils.hashCodeMulti(a,b)              = (31 + a.hashCode()) * 31 + b.hashCode()
              +     * ObjectUtils.hashCodeMulti(a,b,c)            = ((31 + a.hashCode()) * 31 + b.hashCode()) * 31 + c.hashCode()
              +     * 
              + * + * @param objects the objects to obtain the hash code of, may be {@code null} + * @return the hash code of the objects, or zero if null + * @since 3.0 + * @deprecated this method has been replaced by {@code java.util.Objects.hash(Object...)} in Java 7 and will be + * removed in future releases. + */ + @Deprecated + public static int hashCodeMulti(final Object... objects) { + int hash = 1; + if (objects != null) { + for (final Object object : objects) { + final int tmpHash = ObjectUtils.hashCode(object); + hash = hash * 31 + tmpHash; + } + } + return hash; + } + + // Identity ToString + //----------------------------------------------------------------------- + /** + *

              Gets the toString that would be produced by {@code Object} + * if a class did not override toString itself. {@code null} + * will return {@code null}.

              + * + *
              +     * ObjectUtils.identityToString(null)         = null
              +     * ObjectUtils.identityToString("")           = "java.lang.String@1e23"
              +     * ObjectUtils.identityToString(Boolean.TRUE) = "java.lang.Boolean@7fa"
              +     * 
              + * + * @param object the object to create a toString for, may be + * {@code null} + * @return the default toString text, or {@code null} if + * {@code null} passed in + */ + public static String identityToString(final Object object) { + if (object == null) { + return null; + } + final StringBuilder builder = new StringBuilder(); + identityToString(builder, object); + return builder.toString(); + } + + /** + *

              Appends the toString that would be produced by {@code Object} + * if a class did not override toString itself. {@code null} + * will throw a NullPointerException for either of the two parameters.

              + * + *
              +     * ObjectUtils.identityToString(appendable, "")            = appendable.append("java.lang.String@1e23"
              +     * ObjectUtils.identityToString(appendable, Boolean.TRUE)  = appendable.append("java.lang.Boolean@7fa"
              +     * ObjectUtils.identityToString(appendable, Boolean.TRUE)  = appendable.append("java.lang.Boolean@7fa")
              +     * 
              + * + * @param appendable the appendable to append to + * @param object the object to create a toString for + * @throws IOException if an I/O error occurs + * @since 3.2 + */ + public static void identityToString(final Appendable appendable, final Object object) throws IOException { + if (object == null) { + throw new NullPointerException("Cannot get the toString of a null identity"); + } + appendable.append(object.getClass().getName()) + .append('@') + .append(Integer.toHexString(System.identityHashCode(object))); + } + + /** + *

              Appends the toString that would be produced by {@code Object} + * if a class did not override toString itself. {@code null} + * will throw a NullPointerException for either of the two parameters.

              + * + *
              +     * ObjectUtils.identityToString(builder, "")            = builder.append("java.lang.String@1e23"
              +     * ObjectUtils.identityToString(builder, Boolean.TRUE)  = builder.append("java.lang.Boolean@7fa"
              +     * ObjectUtils.identityToString(builder, Boolean.TRUE)  = builder.append("java.lang.Boolean@7fa")
              +     * 
              + * + * @param builder the builder to append to + * @param object the object to create a toString for + * @since 3.2 + */ + public static void identityToString(final StrBuilder builder, final Object object) { + if (object == null) { + throw new NullPointerException("Cannot get the toString of a null identity"); + } + builder.append(object.getClass().getName()) + .append('@') + .append(Integer.toHexString(System.identityHashCode(object))); + } + + /** + *

              Appends the toString that would be produced by {@code Object} + * if a class did not override toString itself. {@code null} + * will throw a NullPointerException for either of the two parameters.

              + * + *
              +     * ObjectUtils.identityToString(buf, "")            = buf.append("java.lang.String@1e23"
              +     * ObjectUtils.identityToString(buf, Boolean.TRUE)  = buf.append("java.lang.Boolean@7fa"
              +     * ObjectUtils.identityToString(buf, Boolean.TRUE)  = buf.append("java.lang.Boolean@7fa")
              +     * 
              + * + * @param buffer the buffer to append to + * @param object the object to create a toString for + * @since 2.4 + */ + public static void identityToString(final StringBuffer buffer, final Object object) { + if (object == null) { + throw new NullPointerException("Cannot get the toString of a null identity"); + } + buffer.append(object.getClass().getName()) + .append('@') + .append(Integer.toHexString(System.identityHashCode(object))); + } + + /** + *

              Appends the toString that would be produced by {@code Object} + * if a class did not override toString itself. {@code null} + * will throw a NullPointerException for either of the two parameters.

              + * + *
              +     * ObjectUtils.identityToString(builder, "")            = builder.append("java.lang.String@1e23"
              +     * ObjectUtils.identityToString(builder, Boolean.TRUE)  = builder.append("java.lang.Boolean@7fa"
              +     * ObjectUtils.identityToString(builder, Boolean.TRUE)  = builder.append("java.lang.Boolean@7fa")
              +     * 
              + * + * @param builder the builder to append to + * @param object the object to create a toString for + * @since 3.2 + */ + public static void identityToString(final StringBuilder builder, final Object object) { + if (object == null) { + throw new NullPointerException("Cannot get the toString of a null identity"); + } + builder.append(object.getClass().getName()) + .append('@') + .append(Integer.toHexString(System.identityHashCode(object))); + } + + // ToString + //----------------------------------------------------------------------- + /** + *

              Gets the {@code toString} of an {@code Object} returning + * an empty string ("") if {@code null} input.

              + * + *
              +     * ObjectUtils.toString(null)         = ""
              +     * ObjectUtils.toString("")           = ""
              +     * ObjectUtils.toString("bat")        = "bat"
              +     * ObjectUtils.toString(Boolean.TRUE) = "true"
              +     * 
              + * + * @see StringUtils#defaultString(String) + * @see String#valueOf(Object) + * @param obj the Object to {@code toString}, may be null + * @return the passed in Object's toString, or {@code ""} if {@code null} input + * @since 2.0 + * @deprecated this method has been replaced by {@code java.util.Objects.toString(Object)} in Java 7 and will be + * removed in future releases. Note however that said method will return "null" for null references, while this + * method returns and empty String. To preserve behavior use {@code java.util.Objects.toString(myObject, "")} + */ + @Deprecated + public static String toString(final Object obj) { + return obj == null ? "" : obj.toString(); + } + + /** + *

              Gets the {@code toString} of an {@code Object} returning + * a specified text if {@code null} input.

              + * + *
              +     * ObjectUtils.toString(null, null)           = null
              +     * ObjectUtils.toString(null, "null")         = "null"
              +     * ObjectUtils.toString("", "null")           = ""
              +     * ObjectUtils.toString("bat", "null")        = "bat"
              +     * ObjectUtils.toString(Boolean.TRUE, "null") = "true"
              +     * 
              + * + * @see StringUtils#defaultString(String,String) + * @see String#valueOf(Object) + * @param obj the Object to {@code toString}, may be null + * @param nullStr the String to return if {@code null} input, may be null + * @return the passed in Object's toString, or {@code nullStr} if {@code null} input + * @since 2.0 + * @deprecated this method has been replaced by {@code java.util.Objects.toString(Object, String)} in Java 7 and + * will be removed in future releases. + */ + @Deprecated + public static String toString(final Object obj, final String nullStr) { + return obj == null ? nullStr : obj.toString(); + } + + // Comparable + //----------------------------------------------------------------------- + /** + *

              Null safe comparison of Comparables.

              + * + * @param type of the values processed by this method + * @param values the set of comparable values, may be null + * @return + *
                + *
              • If any objects are non-null and unequal, the lesser object. + *
              • If all objects are non-null and equal, the first. + *
              • If any of the comparables are null, the lesser of the non-null objects. + *
              • If all the comparables are null, null is returned. + *
              + */ + public static > T min(final T... values) { + T result = null; + if (values != null) { + for (final T value : values) { + if (compare(value, result, true) < 0) { + result = value; + } + } + } + return result; + } + + /** + *

              Null safe comparison of Comparables.

              + * + * @param type of the values processed by this method + * @param values the set of comparable values, may be null + * @return + *
                + *
              • If any objects are non-null and unequal, the greater object. + *
              • If all objects are non-null and equal, the first. + *
              • If any of the comparables are null, the greater of the non-null objects. + *
              • If all the comparables are null, null is returned. + *
              + */ + public static > T max(final T... values) { + T result = null; + if (values != null) { + for (final T value : values) { + if (compare(value, result, false) > 0) { + result = value; + } + } + } + return result; + } + + /** + *

              Null safe comparison of Comparables. + * {@code null} is assumed to be less than a non-{@code null} value.

              + * + * @param type of the values processed by this method + * @param c1 the first comparable, may be null + * @param c2 the second comparable, may be null + * @return a negative value if c1 < c2, zero if c1 = c2 + * and a positive value if c1 > c2 + */ + public static > int compare(final T c1, final T c2) { + return compare(c1, c2, false); + } + + /** + *

              Null safe comparison of Comparables.

              + * + * @param type of the values processed by this method + * @param c1 the first comparable, may be null + * @param c2 the second comparable, may be null + * @param nullGreater if true {@code null} is considered greater + * than a non-{@code null} value or if false {@code null} is + * considered less than a Non-{@code null} value + * @return a negative value if c1 < c2, zero if c1 = c2 + * and a positive value if c1 > c2 + * @see java.util.Comparator#compare(Object, Object) + */ + public static > int compare(final T c1, final T c2, final boolean nullGreater) { + if (c1 == c2) { + return 0; + } else if (c1 == null) { + return nullGreater ? 1 : -1; + } else if (c2 == null) { + return nullGreater ? -1 : 1; + } + return c1.compareTo(c2); + } + + /** + * Find the "best guess" middle value among comparables. If there is an even + * number of total values, the lower of the two middle values will be returned. + * @param type of values processed by this method + * @param items to compare + * @return T at middle position + * @throws NullPointerException if items is {@code null} + * @throws IllegalArgumentException if items is empty or contains {@code null} values + * @since 3.0.1 + */ + public static > T median(final T... items) { + Validate.notEmpty(items); + Validate.noNullElements(items); + final TreeSet sort = new TreeSet(); + Collections.addAll(sort, items); + @SuppressWarnings("unchecked") //we know all items added were T instances + final + T result = (T) sort.toArray()[(sort.size() - 1) / 2]; + return result; + } + + /** + * Find the "best guess" middle value among comparables. If there is an even + * number of total values, the lower of the two middle values will be returned. + * @param type of values processed by this method + * @param comparator to use for comparisons + * @param items to compare + * @return T at middle position + * @throws NullPointerException if items or comparator is {@code null} + * @throws IllegalArgumentException if items is empty or contains {@code null} values + * @since 3.0.1 + */ + public static T median(final Comparator comparator, final T... items) { + Validate.notEmpty(items, "null/empty items"); + Validate.noNullElements(items); + Validate.notNull(comparator, "null comparator"); + final TreeSet sort = new TreeSet(comparator); + Collections.addAll(sort, items); + @SuppressWarnings("unchecked") //we know all items added were T instances + final + T result = (T) sort.toArray()[(sort.size() - 1) / 2]; + return result; + } + + // Mode + //----------------------------------------------------------------------- + /** + * Find the most frequently occurring item. + * + * @param type of values processed by this method + * @param items to check + * @return most populous T, {@code null} if non-unique or no items supplied + * @since 3.0.1 + */ + public static T mode(final T... items) { + if (ArrayUtils.isNotEmpty(items)) { + final HashMap occurrences = new HashMap(items.length); + for (final T t : items) { + final MutableInt count = occurrences.get(t); + if (count == null) { + occurrences.put(t, new MutableInt(1)); + } else { + count.increment(); + } + } + T result = null; + int max = 0; + for (final Map.Entry e : occurrences.entrySet()) { + final int cmp = e.getValue().intValue(); + if (cmp == max) { + result = null; + } else if (cmp > max) { + max = cmp; + result = e.getKey(); + } + } + return result; + } + return null; + } + + // cloning + //----------------------------------------------------------------------- + /** + *

              Clone an object.

              + * + * @param the type of the object + * @param obj the object to clone, null returns null + * @return the clone if the object implements {@link Cloneable} otherwise {@code null} + * @throws CloneFailedException if the object is cloneable and the clone operation fails + * @since 3.0 + */ + public static T clone(final T obj) { + if (obj instanceof Cloneable) { + final Object result; + if (obj.getClass().isArray()) { + final Class componentType = obj.getClass().getComponentType(); + if (!componentType.isPrimitive()) { + result = ((Object[]) obj).clone(); + } else { + int length = Array.getLength(obj); + result = Array.newInstance(componentType, length); + while (length-- > 0) { + Array.set(result, length, Array.get(obj, length)); + } + } + } else { + try { + final Method clone = obj.getClass().getMethod("clone"); + result = clone.invoke(obj); + } catch (final NoSuchMethodException e) { + throw new CloneFailedException("Cloneable type " + + obj.getClass().getName() + + " has no clone method", e); + } catch (final IllegalAccessException e) { + throw new CloneFailedException("Cannot clone Cloneable type " + + obj.getClass().getName(), e); + } catch (final InvocationTargetException e) { + throw new CloneFailedException("Exception cloning Cloneable type " + + obj.getClass().getName(), e.getCause()); + } + } + @SuppressWarnings("unchecked") // OK because input is of type T + final T checked = (T) result; + return checked; + } + + return null; + } + + /** + *

              Clone an object if possible.

              + * + *

              This method is similar to {@link #clone(Object)}, but will return the provided + * instance as the return value instead of {@code null} if the instance + * is not cloneable. This is more convenient if the caller uses different + * implementations (e.g. of a service) and some of the implementations do not allow concurrent + * processing or have state. In such cases the implementation can simply provide a proper + * clone implementation and the caller's code does not have to change.

              + * + * @param the type of the object + * @param obj the object to clone, null returns null + * @return the clone if the object implements {@link Cloneable} otherwise the object itself + * @throws CloneFailedException if the object is cloneable and the clone operation fails + * @since 3.0 + */ + public static T cloneIfPossible(final T obj) { + final T clone = clone(obj); + return clone == null ? obj : clone; + } + + // Null + //----------------------------------------------------------------------- + /** + *

              Class used as a null placeholder where {@code null} + * has another meaning.

              + * + *

              For example, in a {@code HashMap} the + * {@link java.util.HashMap#get(java.lang.Object)} method returns + * {@code null} if the {@code Map} contains {@code null} or if there is + * no matching key. The {@code Null} placeholder can be used to distinguish + * between these two cases.

              + * + *

              Another example is {@code Hashtable}, where {@code null} + * cannot be stored.

              + */ + public static class Null implements Serializable { + /** + * Required for serialization support. Declare serialization compatibility with Commons Lang 1.0 + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 7092611880189329093L; + + /** + * Restricted constructor - singleton. + */ + Null() { + super(); + } + + /** + *

              Ensure singleton.

              + * + * @return the singleton value + */ + private Object readResolve() { + return ObjectUtils.NULL; + } + } + + + // Constants (LANG-816): + /* + These methods ensure constants are not inlined by javac. + For example, typically a developer might declare a constant like so: + + public final static int MAGIC_NUMBER = 5; + + Should a different jar file refer to this, and the MAGIC_NUMBER + is changed a later date (e.g., MAGIC_NUMBER = 6), the different jar + file will need to recompile itself. This is because javac + typically inlines the primitive or String constant directly into + the bytecode, and removes the reference to the MAGIC_NUMBER field. + + To help the other jar (so that it does not need to recompile + when constants are changed) the original developer can declare + their constant using one of the CONST() utility methods, instead: + + public final static int MAGIC_NUMBER = CONST(5); + */ + + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static boolean MAGIC_FLAG = ObjectUtils.CONST(true);
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param v the boolean value to return + * @return the boolean v, unchanged + * @since 3.2 + */ + public static boolean CONST(final boolean v) { return v; } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static byte MAGIC_BYTE = ObjectUtils.CONST((byte) 127);
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param v the byte value to return + * @return the byte v, unchanged + * @since 3.2 + */ + public static byte CONST(final byte v) { return v; } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static byte MAGIC_BYTE = ObjectUtils.CONST_BYTE(127);
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param v the byte literal (as an int) value to return + * @throws IllegalArgumentException if the value passed to v + * is larger than a byte, that is, smaller than -128 or + * larger than 127. + * @return the byte v, unchanged + * @since 3.2 + */ + public static byte CONST_BYTE(final int v) throws IllegalArgumentException { + if (v < Byte.MIN_VALUE || v > Byte.MAX_VALUE) { + throw new IllegalArgumentException("Supplied value must be a valid byte literal between -128 and 127: [" + v + "]"); + } + return (byte) v; + } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static char MAGIC_CHAR = ObjectUtils.CONST('a');
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param v the char value to return + * @return the char v, unchanged + * @since 3.2 + */ + public static char CONST(final char v) { return v; } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static short MAGIC_SHORT = ObjectUtils.CONST((short) 123);
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param v the short value to return + * @return the short v, unchanged + * @since 3.2 + */ + public static short CONST(final short v) { return v; } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static short MAGIC_SHORT = ObjectUtils.CONST_SHORT(127);
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param v the short literal (as an int) value to return + * @throws IllegalArgumentException if the value passed to v + * is larger than a short, that is, smaller than -32768 or + * larger than 32767. + * @return the byte v, unchanged + * @since 3.2 + */ + public static short CONST_SHORT(final int v) throws IllegalArgumentException { + if (v < Short.MIN_VALUE || v > Short.MAX_VALUE) { + throw new IllegalArgumentException("Supplied value must be a valid byte literal between -32768 and 32767: [" + v + "]"); + } + return (short) v; + } + + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static int MAGIC_INT = ObjectUtils.CONST(123);
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param v the int value to return + * @return the int v, unchanged + * @since 3.2 + */ + public static int CONST(final int v) { return v; } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static long MAGIC_LONG = ObjectUtils.CONST(123L);
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param v the long value to return + * @return the long v, unchanged + * @since 3.2 + */ + public static long CONST(final long v) { return v; } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static float MAGIC_FLOAT = ObjectUtils.CONST(1.0f);
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param v the float value to return + * @return the float v, unchanged + * @since 3.2 + */ + public static float CONST(final float v) { return v; } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static double MAGIC_DOUBLE = ObjectUtils.CONST(1.0);
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param v the double value to return + * @return the double v, unchanged + * @since 3.2 + */ + public static double CONST(final double v) { return v; } + + /** + * This method returns the provided value unchanged. + * This can prevent javac from inlining a constant + * field, e.g., + * + *
              +     *     public final static String MAGIC_STRING = ObjectUtils.CONST("abc");
              +     * 
              + * + * This way any jars that refer to this field do not + * have to recompile themselves if the field's value + * changes at some future date. + * + * @param the Object type + * @param v the genericized Object value to return (typically a String). + * @return the genericized Object v, unchanged (typically a String). + * @since 3.2 + */ + public static T CONST(final T v) { return v; } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/RandomStringUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/RandomStringUtils.java new file mode 100644 index 000000000..69422ed1b --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/RandomStringUtils.java @@ -0,0 +1,335 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.util.Random; + +/** + *

              Operations for random {@code String}s.

              + *

              Currently private high surrogate characters are ignored. + * These are Unicode characters that fall between the values 56192 (db80) + * and 56319 (dbff) as we don't know how to handle them. + * High and low surrogates are correctly dealt with - that is if a + * high surrogate is randomly chosen, 55296 (d800) to 56191 (db7f) + * then it is followed by a low surrogate. If a low surrogate is chosen, + * 56320 (dc00) to 57343 (dfff) then it is placed after a randomly + * chosen high surrogate.

              + * + *

              #ThreadSafe#

              + * @since 1.0 + * @version $Id: RandomStringUtils.java 1532684 2013-10-16 08:28:42Z bayard $ + */ +public class RandomStringUtils { + + /** + *

              Random object used by random method. This has to be not local + * to the random method so as to not return the same value in the + * same millisecond.

              + */ + private static final Random RANDOM = new Random(); + + /** + *

              {@code RandomStringUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used as + * {@code RandomStringUtils.random(5);}.

              + * + *

              This constructor is public to permit tools that require a JavaBean instance + * to operate.

              + */ + public RandomStringUtils() { + super(); + } + + // Random + //----------------------------------------------------------------------- + /** + *

              Creates a random string whose length is the number of characters + * specified.

              + * + *

              Characters will be chosen from the set of all characters.

              + * + * @param count the length of random string to create + * @return the random string + */ + public static String random(final int count) { + return random(count, false, false); + } + + /** + *

              Creates a random string whose length is the number of characters + * specified.

              + * + *

              Characters will be chosen from the set of characters whose + * ASCII value is between {@code 32} and {@code 126} (inclusive).

              + * + * @param count the length of random string to create + * @return the random string + */ + public static String randomAscii(final int count) { + return random(count, 32, 127, false, false); + } + + /** + *

              Creates a random string whose length is the number of characters + * specified.

              + * + *

              Characters will be chosen from the set of alphabetic + * characters.

              + * + * @param count the length of random string to create + * @return the random string + */ + public static String randomAlphabetic(final int count) { + return random(count, true, false); + } + + /** + *

              Creates a random string whose length is the number of characters + * specified.

              + * + *

              Characters will be chosen from the set of alpha-numeric + * characters.

              + * + * @param count the length of random string to create + * @return the random string + */ + public static String randomAlphanumeric(final int count) { + return random(count, true, true); + } + + /** + *

              Creates a random string whose length is the number of characters + * specified.

              + * + *

              Characters will be chosen from the set of numeric + * characters.

              + * + * @param count the length of random string to create + * @return the random string + */ + public static String randomNumeric(final int count) { + return random(count, false, true); + } + + /** + *

              Creates a random string whose length is the number of characters + * specified.

              + * + *

              Characters will be chosen from the set of alpha-numeric + * characters as indicated by the arguments.

              + * + * @param count the length of random string to create + * @param letters if {@code true}, generated string may include + * alphabetic characters + * @param numbers if {@code true}, generated string may include + * numeric characters + * @return the random string + */ + public static String random(final int count, final boolean letters, final boolean numbers) { + return random(count, 0, 0, letters, numbers); + } + + /** + *

              Creates a random string whose length is the number of characters + * specified.

              + * + *

              Characters will be chosen from the set of alpha-numeric + * characters as indicated by the arguments.

              + * + * @param count the length of random string to create + * @param start the position in set of chars to start at + * @param end the position in set of chars to end before + * @param letters if {@code true}, generated string may include + * alphabetic characters + * @param numbers if {@code true}, generated string may include + * numeric characters + * @return the random string + */ + public static String random(final int count, final int start, final int end, final boolean letters, final boolean numbers) { + return random(count, start, end, letters, numbers, null, RANDOM); + } + + /** + *

              Creates a random string based on a variety of options, using + * default source of randomness.

              + * + *

              This method has exactly the same semantics as + * {@link #random(int,int,int,boolean,boolean,char[],Random)}, but + * instead of using an externally supplied source of randomness, it uses + * the internal static {@link Random} instance.

              + * + * @param count the length of random string to create + * @param start the position in set of chars to start at + * @param end the position in set of chars to end before + * @param letters only allow letters? + * @param numbers only allow numbers? + * @param chars the set of chars to choose randoms from. + * If {@code null}, then it will use the set of all chars. + * @return the random string + * @throws ArrayIndexOutOfBoundsException if there are not + * {@code (end - start) + 1} characters in the set array. + */ + public static String random(final int count, final int start, final int end, final boolean letters, final boolean numbers, final char... chars) { + return random(count, start, end, letters, numbers, chars, RANDOM); + } + + /** + *

              Creates a random string based on a variety of options, using + * supplied source of randomness.

              + * + *

              If start and end are both {@code 0}, start and end are set + * to {@code ' '} and {@code 'z'}, the ASCII printable + * characters, will be used, unless letters and numbers are both + * {@code false}, in which case, start and end are set to + * {@code 0} and {@code Integer.MAX_VALUE}. + * + *

              If set is not {@code null}, characters between start and + * end are chosen.

              + * + *

              This method accepts a user-supplied {@link Random} + * instance to use as a source of randomness. By seeding a single + * {@link Random} instance with a fixed seed and using it for each call, + * the same random sequence of strings can be generated repeatedly + * and predictably.

              + * + * @param count the length of random string to create + * @param start the position in set of chars to start at + * @param end the position in set of chars to end before + * @param letters only allow letters? + * @param numbers only allow numbers? + * @param chars the set of chars to choose randoms from, must not be empty. + * If {@code null}, then it will use the set of all chars. + * @param random a source of randomness. + * @return the random string + * @throws ArrayIndexOutOfBoundsException if there are not + * {@code (end - start) + 1} characters in the set array. + * @throws IllegalArgumentException if {@code count} < 0 or the provided chars array is empty. + * @since 2.0 + */ + public static String random(int count, int start, int end, final boolean letters, final boolean numbers, + final char[] chars, final Random random) { + if (count == 0) { + return ""; + } else if (count < 0) { + throw new IllegalArgumentException("Requested random string length " + count + " is less than 0."); + } + if (chars != null && chars.length == 0) { + throw new IllegalArgumentException("The chars array must not be empty"); + } + + if (start == 0 && end == 0) { + if (chars != null) { + end = chars.length; + } else { + if (!letters && !numbers) { + end = Integer.MAX_VALUE; + } else { + end = 'z' + 1; + start = ' '; + } + } + } else { + if (end <= start) { + throw new IllegalArgumentException("Parameter end (" + end + ") must be greater than start (" + start + ")"); + } + } + + final char[] buffer = new char[count]; + final int gap = end - start; + + while (count-- != 0) { + char ch; + if (chars == null) { + ch = (char) (random.nextInt(gap) + start); + } else { + ch = chars[random.nextInt(gap) + start]; + } + if (letters && Character.isLetter(ch) + || numbers && Character.isDigit(ch) + || !letters && !numbers) { + if(ch >= 56320 && ch <= 57343) { + if(count == 0) { + count++; + } else { + // low surrogate, insert high surrogate after putting it in + buffer[count] = ch; + count--; + buffer[count] = (char) (55296 + random.nextInt(128)); + } + } else if(ch >= 55296 && ch <= 56191) { + if(count == 0) { + count++; + } else { + // high surrogate, insert low surrogate before putting it in + buffer[count] = (char) (56320 + random.nextInt(128)); + count--; + buffer[count] = ch; + } + } else if(ch >= 56192 && ch <= 56319) { + // private high surrogate, no effing clue, so skip it + count++; + } else { + buffer[count] = ch; + } + } else { + count++; + } + } + return new String(buffer); + } + + /** + *

              Creates a random string whose length is the number of characters + * specified.

              + * + *

              Characters will be chosen from the set of characters + * specified by the string, must not be empty. + * If null, the set of all characters is used.

              + * + * @param count the length of random string to create + * @param chars the String containing the set of characters to use, + * may be null, but must not be empty + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0 or the string is empty. + */ + public static String random(final int count, final String chars) { + if (chars == null) { + return random(count, 0, 0, false, false, null, RANDOM); + } + return random(count, chars.toCharArray()); + } + + /** + *

              Creates a random string whose length is the number of characters + * specified.

              + * + *

              Characters will be chosen from the set of characters specified.

              + * + * @param count the length of random string to create + * @param chars the character array containing the set of characters to use, + * may be null + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + */ + public static String random(final int count, final char... chars) { + if (chars == null) { + return random(count, 0, 0, false, false, null, RANDOM); + } + return random(count, 0, chars.length, false, false, chars, RANDOM); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/RandomUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/RandomUtils.java new file mode 100644 index 000000000..3d0249217 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/RandomUtils.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.util.Random; + +/** + *

              Utility library that supplements the standard {@link Random} class.

              + * + * @since 3.3 + * + * @version $Id: RandomUtils.java 1606051 2014-06-27 12:22:17Z ggregory $ + */ +public class RandomUtils { + + /** + * Random object used by random method. This has to be not local to the + * random method so as to not return the same value in the same millisecond. + */ + private static final Random RANDOM = new Random(); + + /** + *

              + * {@code RandomUtils} instances should NOT be constructed in standard + * programming. Instead, the class should be used as + * {@code RandomUtils.nextBytes(5);}. + *

              + * + *

              + * This constructor is public to permit tools that require a JavaBean + * instance to operate. + *

              + */ + public RandomUtils() { + super(); + } + + /** + *

              + * Creates an array of random bytes. + *

              + * + * @param count + * the size of the returned array + * @return the random byte array + * @throws IllegalArgumentException if {@code count} is negative + */ + public static byte[] nextBytes(final int count) { + Validate.isTrue(count >= 0, "Count cannot be negative."); + + final byte[] result = new byte[count]; + RANDOM.nextBytes(result); + return result; + } + + /** + *

              + * Returns a random integer within the specified range. + *

              + * + * @param startInclusive + * the smallest value that can be returned, must be non-negative + * @param endExclusive + * the upper bound (not included) + * @throws IllegalArgumentException + * if {@code startInclusive > endExclusive} or if + * {@code startInclusive} is negative + * @return the random integer + */ + public static int nextInt(final int startInclusive, final int endExclusive) { + Validate.isTrue(endExclusive >= startInclusive, + "Start value must be smaller or equal to end value."); + Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); + + if (startInclusive == endExclusive) { + return startInclusive; + } + + return startInclusive + RANDOM.nextInt(endExclusive - startInclusive); + } + + /** + *

              + * Returns a random long within the specified range. + *

              + * + * @param startInclusive + * the smallest value that can be returned, must be non-negative + * @param endExclusive + * the upper bound (not included) + * @throws IllegalArgumentException + * if {@code startInclusive > endExclusive} or if + * {@code startInclusive} is negative + * @return the random long + */ + public static long nextLong(final long startInclusive, final long endExclusive) { + Validate.isTrue(endExclusive >= startInclusive, + "Start value must be smaller or equal to end value."); + Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); + + if (startInclusive == endExclusive) { + return startInclusive; + } + + return (long) nextDouble(startInclusive, endExclusive); + } + + + /** + *

              + * Returns a random double within the specified range. + *

              + * + * @param startInclusive + * the smallest value that can be returned, must be non-negative + * @param endInclusive + * the upper bound (included) + * @throws IllegalArgumentException + * if {@code startInclusive > endInclusive} or if + * {@code startInclusive} is negative + * @return the random double + */ + public static double nextDouble(final double startInclusive, final double endInclusive) { + Validate.isTrue(endInclusive >= startInclusive, + "Start value must be smaller or equal to end value."); + Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); + + if (startInclusive == endInclusive) { + return startInclusive; + } + + return startInclusive + ((endInclusive - startInclusive) * RANDOM.nextDouble()); + } + + /** + *

              + * Returns a random float within the specified range. + *

              + * + * @param startInclusive + * the smallest value that can be returned, must be non-negative + * @param endInclusive + * the upper bound (included) + * @throws IllegalArgumentException + * if {@code startInclusive > endInclusive} or if + * {@code startInclusive} is negative + * @return the random float + */ + public static float nextFloat(final float startInclusive, final float endInclusive) { + Validate.isTrue(endInclusive >= startInclusive, + "Start value must be smaller or equal to end value."); + Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); + + if (startInclusive == endInclusive) { + return startInclusive; + } + + return startInclusive + ((endInclusive - startInclusive) * RANDOM.nextFloat()); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Range.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Range.java new file mode 100644 index 000000000..b45dcacb5 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Range.java @@ -0,0 +1,496 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.io.Serializable; +import java.util.Comparator; + +/** + *

              An immutable range of objects from a minimum to maximum point inclusive.

              + * + *

              The objects need to either be implementations of {@code Comparable} + * or you need to supply a {@code Comparator}.

              + * + *

              #ThreadSafe# if the objects and comparator are thread-safe

              + * + * @since 3.0 + * @version $Id: Range.java 1565243 2014-02-06 13:37:12Z sebb $ + */ +public final class Range implements Serializable { + + /** + * Serialization version. + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** + * The ordering scheme used in this range. + */ + private final Comparator comparator; + /** + * The minimum value in this range (inclusive). + */ + private final T minimum; + /** + * The maximum value in this range (inclusive). + */ + private final T maximum; + /** + * Cached output hashCode (class is immutable). + */ + private transient int hashCode; + /** + * Cached output toString (class is immutable). + */ + private transient String toString; + + /** + *

              Obtains a range using the specified element as both the minimum + * and maximum in this range.

              + * + *

              The range uses the natural ordering of the elements to determine where + * values lie in the range.

              + * + * @param the type of the elements in this range + * @param element the value to use for this range, not null + * @return the range object, not null + * @throws IllegalArgumentException if the element is null + * @throws ClassCastException if the element is not {@code Comparable} + */ + public static > Range is(final T element) { + return between(element, element, null); + } + + /** + *

              Obtains a range using the specified element as both the minimum + * and maximum in this range.

              + * + *

              The range uses the specified {@code Comparator} to determine where + * values lie in the range.

              + * + * @param the type of the elements in this range + * @param element the value to use for this range, must not be {@code null} + * @param comparator the comparator to be used, null for natural ordering + * @return the range object, not null + * @throws IllegalArgumentException if the element is null + * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable} + */ + public static Range is(final T element, final Comparator comparator) { + return between(element, element, comparator); + } + + /** + *

              Obtains a range with the specified minimum and maximum values (both inclusive).

              + * + *

              The range uses the natural ordering of the elements to determine where + * values lie in the range.

              + * + *

              The arguments may be passed in the order (min,max) or (max,min). + * The getMinimum and getMaximum methods will return the correct values.

              + * + * @param the type of the elements in this range + * @param fromInclusive the first value that defines the edge of the range, inclusive + * @param toInclusive the second value that defines the edge of the range, inclusive + * @return the range object, not null + * @throws IllegalArgumentException if either element is null + * @throws ClassCastException if the elements are not {@code Comparable} + */ + public static > Range between(final T fromInclusive, final T toInclusive) { + return between(fromInclusive, toInclusive, null); + } + + /** + *

              Obtains a range with the specified minimum and maximum values (both inclusive).

              + * + *

              The range uses the specified {@code Comparator} to determine where + * values lie in the range.

              + * + *

              The arguments may be passed in the order (min,max) or (max,min). + * The getMinimum and getMaximum methods will return the correct values.

              + * + * @param the type of the elements in this range + * @param fromInclusive the first value that defines the edge of the range, inclusive + * @param toInclusive the second value that defines the edge of the range, inclusive + * @param comparator the comparator to be used, null for natural ordering + * @return the range object, not null + * @throws IllegalArgumentException if either element is null + * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable} + */ + public static Range between(final T fromInclusive, final T toInclusive, final Comparator comparator) { + return new Range(fromInclusive, toInclusive, comparator); + } + + /** + * Creates an instance. + * + * @param element1 the first element, not null + * @param element2 the second element, not null + * @param comp the comparator to be used, null for natural ordering + */ + @SuppressWarnings("unchecked") + private Range(final T element1, final T element2, final Comparator comp) { + if (element1 == null || element2 == null) { + throw new IllegalArgumentException("Elements in a range must not be null: element1=" + + element1 + ", element2=" + element2); + } + if (comp == null) { + this.comparator = ComparableComparator.INSTANCE; + } else { + this.comparator = comp; + } + if (this.comparator.compare(element1, element2) < 1) { + this.minimum = element1; + this.maximum = element2; + } else { + this.minimum = element2; + this.maximum = element1; + } + } + + // Accessors + //-------------------------------------------------------------------- + + /** + *

              Gets the minimum value in this range.

              + * + * @return the minimum value in this range, not null + */ + public T getMinimum() { + return minimum; + } + + /** + *

              Gets the maximum value in this range.

              + * + * @return the maximum value in this range, not null + */ + public T getMaximum() { + return maximum; + } + + /** + *

              Gets the comparator being used to determine if objects are within the range.

              + * + *

              Natural ordering uses an internal comparator implementation, thus this + * method never returns null. See {@link #isNaturalOrdering()}.

              + * + * @return the comparator being used, not null + */ + public Comparator getComparator() { + return comparator; + } + + /** + *

              Whether or not the Range is using the natural ordering of the elements.

              + * + *

              Natural ordering uses an internal comparator implementation, thus this + * method is the only way to check if a null comparator was specified.

              + * + * @return true if using natural ordering + */ + public boolean isNaturalOrdering() { + return comparator == ComparableComparator.INSTANCE; + } + + // Element tests + //-------------------------------------------------------------------- + + /** + *

              Checks whether the specified element occurs within this range.

              + * + * @param element the element to check for, null returns false + * @return true if the specified element occurs within this range + */ + public boolean contains(final T element) { + if (element == null) { + return false; + } + return comparator.compare(element, minimum) > -1 && comparator.compare(element, maximum) < 1; + } + + /** + *

              Checks whether this range is after the specified element.

              + * + * @param element the element to check for, null returns false + * @return true if this range is entirely after the specified element + */ + public boolean isAfter(final T element) { + if (element == null) { + return false; + } + return comparator.compare(element, minimum) < 0; + } + + /** + *

              Checks whether this range starts with the specified element.

              + * + * @param element the element to check for, null returns false + * @return true if the specified element occurs within this range + */ + public boolean isStartedBy(final T element) { + if (element == null) { + return false; + } + return comparator.compare(element, minimum) == 0; + } + + /** + *

              Checks whether this range starts with the specified element.

              + * + * @param element the element to check for, null returns false + * @return true if the specified element occurs within this range + */ + public boolean isEndedBy(final T element) { + if (element == null) { + return false; + } + return comparator.compare(element, maximum) == 0; + } + + /** + *

              Checks whether this range is before the specified element.

              + * + * @param element the element to check for, null returns false + * @return true if this range is entirely before the specified element + */ + public boolean isBefore(final T element) { + if (element == null) { + return false; + } + return comparator.compare(element, maximum) > 0; + } + + /** + *

              Checks where the specified element occurs relative to this range.

              + * + *

              The API is reminiscent of the Comparable interface returning {@code -1} if + * the element is before the range, {@code 0} if contained within the range and + * {@code 1} if the element is after the range.

              + * + * @param element the element to check for, not null + * @return -1, 0 or +1 depending on the element's location relative to the range + */ + public int elementCompareTo(final T element) { + if (element == null) { + // Comparable API says throw NPE on null + throw new NullPointerException("Element is null"); + } + if (isAfter(element)) { + return -1; + } else if (isBefore(element)) { + return 1; + } else { + return 0; + } + } + + // Range tests + //-------------------------------------------------------------------- + + /** + *

              Checks whether this range contains all the elements of the specified range.

              + * + *

              This method may fail if the ranges have two different comparators or element types.

              + * + * @param otherRange the range to check, null returns false + * @return true if this range contains the specified range + * @throws RuntimeException if ranges cannot be compared + */ + public boolean containsRange(final Range otherRange) { + if (otherRange == null) { + return false; + } + return contains(otherRange.minimum) + && contains(otherRange.maximum); + } + + /** + *

              Checks whether this range is completely after the specified range.

              + * + *

              This method may fail if the ranges have two different comparators or element types.

              + * + * @param otherRange the range to check, null returns false + * @return true if this range is completely after the specified range + * @throws RuntimeException if ranges cannot be compared + */ + public boolean isAfterRange(final Range otherRange) { + if (otherRange == null) { + return false; + } + return isAfter(otherRange.maximum); + } + + /** + *

              Checks whether this range is overlapped by the specified range.

              + * + *

              Two ranges overlap if there is at least one element in common.

              + * + *

              This method may fail if the ranges have two different comparators or element types.

              + * + * @param otherRange the range to test, null returns false + * @return true if the specified range overlaps with this + * range; otherwise, {@code false} + * @throws RuntimeException if ranges cannot be compared + */ + public boolean isOverlappedBy(final Range otherRange) { + if (otherRange == null) { + return false; + } + return otherRange.contains(minimum) + || otherRange.contains(maximum) + || contains(otherRange.minimum); + } + + /** + *

              Checks whether this range is completely before the specified range.

              + * + *

              This method may fail if the ranges have two different comparators or element types.

              + * + * @param otherRange the range to check, null returns false + * @return true if this range is completely before the specified range + * @throws RuntimeException if ranges cannot be compared + */ + public boolean isBeforeRange(final Range otherRange) { + if (otherRange == null) { + return false; + } + return isBefore(otherRange.minimum); + } + + /** + * Calculate the intersection of {@code this} and an overlapping Range. + * @param other overlapping Range + * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal) + * @throws IllegalArgumentException if {@code other} does not overlap {@code this} + * @since 3.0.1 + */ + public Range intersectionWith(final Range other) { + if (!this.isOverlappedBy(other)) { + throw new IllegalArgumentException(String.format( + "Cannot calculate intersection with non-overlapping range %s", other)); + } + if (this.equals(other)) { + return this; + } + final T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum; + final T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum; + return between(min, max, getComparator()); + } + + // Basics + //-------------------------------------------------------------------- + + /** + *

              Compares this range to another object to test if they are equal.

              . + * + *

              To be equal, the minimum and maximum values must be equal, which + * ignores any differences in the comparator.

              + * + * @param obj the reference object with which to compare + * @return true if this object is equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } else if (obj == null || obj.getClass() != getClass()) { + return false; + } else { + @SuppressWarnings("unchecked") // OK because we checked the class above + final + Range range = (Range) obj; + return minimum.equals(range.minimum) && + maximum.equals(range.maximum); + } + } + + /** + *

              Gets a suitable hash code for the range.

              + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + int result = hashCode; + if (hashCode == 0) { + result = 17; + result = 37 * result + getClass().hashCode(); + result = 37 * result + minimum.hashCode(); + result = 37 * result + maximum.hashCode(); + hashCode = result; + } + return result; + } + + /** + *

              Gets the range as a {@code String}.

              + * + *

              The format of the String is '[min..max]'.

              + * + * @return the {@code String} representation of this range + */ + @Override + public String toString() { + String result = toString; + if (result == null) { + final StringBuilder buf = new StringBuilder(32); + buf.append('['); + buf.append(minimum); + buf.append(".."); + buf.append(maximum); + buf.append(']'); + result = buf.toString(); + toString = result; + } + return result; + } + + /** + *

              Formats the receiver using the given format.

              + * + *

              This uses {@link java.util.Formattable} to perform the formatting. Three variables may + * be used to embed the minimum, maximum and comparator. + * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element + * and {@code %3$s} for the comparator. + * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.

              + * + * @param format the format string, optionally containing {@code %1$s}, {@code %2$s} and {@code %3$s}, not null + * @return the formatted string, not null + */ + public String toString(final String format) { + return String.format(format, minimum, maximum, comparator); + } + + //----------------------------------------------------------------------- + @SuppressWarnings({"rawtypes", "unchecked"}) + private enum ComparableComparator implements Comparator { + INSTANCE; + /** + * Comparable based compare implementation. + * + * @param obj1 left hand side of comparison + * @param obj2 right hand side of comparison + * @return negative, 0, positive comparison value + */ + @Override + public int compare(final Object obj1, final Object obj2) { + return ((Comparable) obj1).compareTo(obj2); + } + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SerializationException.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SerializationException.java new file mode 100644 index 000000000..3deecd29f --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SerializationException.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +/** + *

              Exception thrown when the Serialization process fails.

              + * + *

              The original error is wrapped within this one.

              + * + *

              #NotThreadSafe# because Throwable is not thread-safe

              + * @since 1.0 + * @version $Id: SerializationException.java 1436768 2013-01-22 07:07:42Z ggregory $ + */ +public class SerializationException extends RuntimeException { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 4029025366392702726L; + + /** + *

              Constructs a new {@code SerializationException} without specified + * detail message.

              + */ + public SerializationException() { + super(); + } + + /** + *

              Constructs a new {@code SerializationException} with specified + * detail message.

              + * + * @param msg The error message. + */ + public SerializationException(final String msg) { + super(msg); + } + + /** + *

              Constructs a new {@code SerializationException} with specified + * nested {@code Throwable}.

              + * + * @param cause The {@code Exception} or {@code Error} + * that caused this exception to be thrown. + */ + public SerializationException(final Throwable cause) { + super(cause); + } + + /** + *

              Constructs a new {@code SerializationException} with specified + * detail message and nested {@code Throwable}.

              + * + * @param msg The error message. + * @param cause The {@code Exception} or {@code Error} + * that caused this exception to be thrown. + */ + public SerializationException(final String msg, final Throwable cause) { + super(msg, cause); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SerializationUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SerializationUtils.java new file mode 100644 index 000000000..3473f807e --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SerializationUtils.java @@ -0,0 +1,338 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + *

              Assists with the serialization process and performs additional functionality based + * on serialization.

              + * + *
                + *
              • Deep clone using serialization + *
              • Serialize managing finally and IOException + *
              • Deserialize managing finally and IOException + *
              + * + *

              This class throws exceptions for invalid {@code null} inputs. + * Each method documents its behaviour in more detail.

              + * + *

              #ThreadSafe#

              + * @since 1.0 + * @version $Id: SerializationUtils.java 1653307 2015-01-20 17:30:11Z britter $ + */ +public class SerializationUtils { + + /** + *

              SerializationUtils instances should NOT be constructed in standard programming. + * Instead, the class should be used as {@code SerializationUtils.clone(object)}.

              + * + *

              This constructor is public to permit tools that require a JavaBean instance + * to operate.

              + * @since 2.0 + */ + public SerializationUtils() { + super(); + } + + // Clone + //----------------------------------------------------------------------- + /** + *

              Deep clone an {@code Object} using serialization.

              + * + *

              This is many times slower than writing clone methods by hand + * on all objects in your object graph. However, for complex object + * graphs, or for those that don't support deep cloning this can + * be a simple alternative implementation. Of course all the objects + * must be {@code Serializable}.

              + * + * @param the type of the object involved + * @param object the {@code Serializable} object to clone + * @return the cloned object + * @throws SerializationException (runtime) if the serialization fails + */ + public static T clone(final T object) { + if (object == null) { + return null; + } + final byte[] objectData = serialize(object); + final ByteArrayInputStream bais = new ByteArrayInputStream(objectData); + + ClassLoaderAwareObjectInputStream in = null; + try { + // stream closed in the finally + in = new ClassLoaderAwareObjectInputStream(bais, object.getClass().getClassLoader()); + /* + * when we serialize and deserialize an object, + * it is reasonable to assume the deserialized object + * is of the same type as the original serialized object + */ + @SuppressWarnings("unchecked") // see above + final T readObject = (T) in.readObject(); + return readObject; + + } catch (final ClassNotFoundException ex) { + throw new SerializationException("ClassNotFoundException while reading cloned object data", ex); + } catch (final IOException ex) { + throw new SerializationException("IOException while reading cloned object data", ex); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (final IOException ex) { + throw new SerializationException("IOException on closing cloned object data InputStream.", ex); + } + } + } + + /** + * Performs a serialization roundtrip. Serializes and deserializes the given object, great for testing objects that + * implement {@link Serializable}. + * + * @param + * the type of the object involved + * @param msg + * the object to roundtrip + * @return the serialized and deseralized object + * @since 3.3 + */ + public static T roundtrip(final T msg) { + return (T) SerializationUtils.deserialize(SerializationUtils.serialize(msg)); + } + + // Serialize + //----------------------------------------------------------------------- + /** + *

              Serializes an {@code Object} to the specified stream.

              + * + *

              The stream will be closed once the object is written. + * This avoids the need for a finally clause, and maybe also exception + * handling, in the application code.

              + * + *

              The stream passed in is not buffered internally within this method. + * This is the responsibility of your application if desired.

              + * + * @param obj the object to serialize to bytes, may be null + * @param outputStream the stream to write to, must not be null + * @throws IllegalArgumentException if {@code outputStream} is {@code null} + * @throws SerializationException (runtime) if the serialization fails + */ + public static void serialize(final Serializable obj, final OutputStream outputStream) { + if (outputStream == null) { + throw new IllegalArgumentException("The OutputStream must not be null"); + } + ObjectOutputStream out = null; + try { + // stream closed in the finally + out = new ObjectOutputStream(outputStream); + out.writeObject(obj); + + } catch (final IOException ex) { + throw new SerializationException(ex); + } finally { + try { + if (out != null) { + out.close(); + } + } catch (final IOException ex) { // NOPMD + // ignore close exception + } + } + } + + /** + *

              Serializes an {@code Object} to a byte array for + * storage/serialization.

              + * + * @param obj the object to serialize to bytes + * @return a byte[] with the converted Serializable + * @throws SerializationException (runtime) if the serialization fails + */ + public static byte[] serialize(final Serializable obj) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(512); + serialize(obj, baos); + return baos.toByteArray(); + } + + // Deserialize + //----------------------------------------------------------------------- + /** + *

              + * Deserializes an {@code Object} from the specified stream. + *

              + * + *

              + * The stream will be closed once the object is written. This avoids the need for a finally clause, and maybe also + * exception handling, in the application code. + *

              + * + *

              + * The stream passed in is not buffered internally within this method. This is the responsibility of your + * application if desired. + *

              + * + *

              + * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site. + * Without Generics in this declaration, the call site must type cast and can cause the same ClassCastException. + * Note that in both cases, the ClassCastException is in the call site, not in this method. + *

              + * + * @param the object type to be deserialized + * @param inputStream + * the serialized object input stream, must not be null + * @return the deserialized object + * @throws IllegalArgumentException + * if {@code inputStream} is {@code null} + * @throws SerializationException + * (runtime) if the serialization fails + */ + public static T deserialize(final InputStream inputStream) { + if (inputStream == null) { + throw new IllegalArgumentException("The InputStream must not be null"); + } + ObjectInputStream in = null; + try { + // stream closed in the finally + in = new ObjectInputStream(inputStream); + @SuppressWarnings("unchecked") // may fail with CCE if serialised form is incorrect + final T obj = (T) in.readObject(); + return obj; + + } catch (final ClassCastException ex) { + throw new SerializationException(ex); + } catch (final ClassNotFoundException ex) { + throw new SerializationException(ex); + } catch (final IOException ex) { + throw new SerializationException(ex); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (final IOException ex) { // NOPMD + // ignore close exception + } + } + } + + /** + *

              + * Deserializes a single {@code Object} from an array of bytes. + *

              + * + *

              + * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site. + * Without Generics in this declaration, the call site must type cast and can cause the same ClassCastException. + * Note that in both cases, the ClassCastException is in the call site, not in this method. + *

              + * + * @param the object type to be deserialized + * @param objectData + * the serialized object, must not be null + * @return the deserialized object + * @throws IllegalArgumentException + * if {@code objectData} is {@code null} + * @throws SerializationException + * (runtime) if the serialization fails + */ + public static T deserialize(final byte[] objectData) { + if (objectData == null) { + throw new IllegalArgumentException("The byte[] must not be null"); + } + return SerializationUtils.deserialize(new ByteArrayInputStream(objectData)); + } + + /** + *

              Custom specialization of the standard JDK {@link java.io.ObjectInputStream} + * that uses a custom ClassLoader to resolve a class. + * If the specified ClassLoader is not able to resolve the class, + * the context classloader of the current thread will be used. + * This way, the standard deserialization work also in web-application + * containers and application servers, no matter in which of the + * ClassLoader the particular class that encapsulates + * serialization/deserialization lives.

              + * + *

              For more in-depth information about the problem for which this + * class here is a workaround, see the JIRA issue LANG-626.

              + */ + static class ClassLoaderAwareObjectInputStream extends ObjectInputStream { + private static final Map> primitiveTypes = + new HashMap>(); + private final ClassLoader classLoader; + + /** + * Constructor. + * @param in The InputStream. + * @param classLoader classloader to use + * @throws IOException if an I/O error occurs while reading stream header. + * @see java.io.ObjectInputStream + */ + public ClassLoaderAwareObjectInputStream(final InputStream in, final ClassLoader classLoader) throws IOException { + super(in); + this.classLoader = classLoader; + + primitiveTypes.put("byte", byte.class); + primitiveTypes.put("short", short.class); + primitiveTypes.put("int", int.class); + primitiveTypes.put("long", long.class); + primitiveTypes.put("float", float.class); + primitiveTypes.put("double", double.class); + primitiveTypes.put("boolean", boolean.class); + primitiveTypes.put("char", char.class); + primitiveTypes.put("void", void.class); + } + + /** + * Overriden version that uses the parametrized ClassLoader or the ClassLoader + * of the current Thread to resolve the class. + * @param desc An instance of class ObjectStreamClass. + * @return A Class object corresponding to desc. + * @throws IOException Any of the usual Input/Output exceptions. + * @throws ClassNotFoundException If class of a serialized object cannot be found. + */ + @Override + protected Class resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException { + final String name = desc.getName(); + try { + return Class.forName(name, false, classLoader); + } catch (final ClassNotFoundException ex) { + try { + return Class.forName(name, false, Thread.currentThread().getContextClassLoader()); + } catch (final ClassNotFoundException cnfe) { + final Class cls = primitiveTypes.get(name); + if (cls != null) { + return cls; + } + throw cnfe; + } + } + } + + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/StringEscapeUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/StringEscapeUtils.java new file mode 100644 index 000000000..4e062250d --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/StringEscapeUtils.java @@ -0,0 +1,805 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.io.IOException; +import java.io.Writer; + +import com.fr.third.org.apache.commons.lang3.text.translate.AggregateTranslator; +import com.fr.third.org.apache.commons.lang3.text.translate.CharSequenceTranslator; +import com.fr.third.org.apache.commons.lang3.text.translate.EntityArrays; +import com.fr.third.org.apache.commons.lang3.text.translate.JavaUnicodeEscaper; +import com.fr.third.org.apache.commons.lang3.text.translate.LookupTranslator; +import com.fr.third.org.apache.commons.lang3.text.translate.NumericEntityEscaper; +import com.fr.third.org.apache.commons.lang3.text.translate.NumericEntityUnescaper; +import com.fr.third.org.apache.commons.lang3.text.translate.OctalUnescaper; +import com.fr.third.org.apache.commons.lang3.text.translate.UnicodeUnescaper; +import com.fr.third.org.apache.commons.lang3.text.translate.UnicodeUnpairedSurrogateRemover; + +/** + *

              Escapes and unescapes {@code String}s for + * Java, Java Script, HTML and XML.

              + * + *

              #ThreadSafe#

              + * @since 2.0 + * @version $Id: StringEscapeUtils.java 1630076 2014-10-08 11:49:54Z djones $ + */ +public class StringEscapeUtils { + + /* ESCAPE TRANSLATORS */ + + /** + * Translator object for escaping Java. + * + * While {@link #escapeJava(String)} is the expected method of use, this + * object allows the Java escaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator ESCAPE_JAVA = + new LookupTranslator( + new String[][] { + {"\"", "\\\""}, + {"\\", "\\\\"}, + }).with( + new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE()) + ).with( + JavaUnicodeEscaper.outsideOf(32, 0x7f) + ); + + /** + * Translator object for escaping EcmaScript/JavaScript. + * + * While {@link #escapeEcmaScript(String)} is the expected method of use, this + * object allows the EcmaScript escaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator ESCAPE_ECMASCRIPT = + new AggregateTranslator( + new LookupTranslator( + new String[][] { + {"'", "\\'"}, + {"\"", "\\\""}, + {"\\", "\\\\"}, + {"/", "\\/"} + }), + new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE()), + JavaUnicodeEscaper.outsideOf(32, 0x7f) + ); + + /** + * Translator object for escaping Json. + * + * While {@link #escapeJson(String)} is the expected method of use, this + * object allows the Json escaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.2 + */ + public static final CharSequenceTranslator ESCAPE_JSON = + new AggregateTranslator( + new LookupTranslator( + new String[][] { + {"\"", "\\\""}, + {"\\", "\\\\"}, + {"/", "\\/"} + }), + new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE()), + JavaUnicodeEscaper.outsideOf(32, 0x7f) + ); + + /** + * Translator object for escaping XML. + * + * While {@link #escapeXml(String)} is the expected method of use, this + * object allows the XML escaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + * @deprecated use {@link #ESCAPE_XML10} or {@link #ESCAPE_XML11} instead. + */ + @Deprecated + public static final CharSequenceTranslator ESCAPE_XML = + new AggregateTranslator( + new LookupTranslator(EntityArrays.BASIC_ESCAPE()), + new LookupTranslator(EntityArrays.APOS_ESCAPE()) + ); + + /** + * Translator object for escaping XML 1.0. + * + * While {@link #escapeXml10(String)} is the expected method of use, this + * object allows the XML escaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.3 + */ + public static final CharSequenceTranslator ESCAPE_XML10 = + new AggregateTranslator( + new LookupTranslator(EntityArrays.BASIC_ESCAPE()), + new LookupTranslator(EntityArrays.APOS_ESCAPE()), + new LookupTranslator( + new String[][] { + { "\u0000", "" }, + { "\u0001", "" }, + { "\u0002", "" }, + { "\u0003", "" }, + { "\u0004", "" }, + { "\u0005", "" }, + { "\u0006", "" }, + { "\u0007", "" }, + { "\u0008", "" }, + { "\u000b", "" }, + { "\u000c", "" }, + { "\u000e", "" }, + { "\u000f", "" }, + { "\u0010", "" }, + { "\u0011", "" }, + { "\u0012", "" }, + { "\u0013", "" }, + { "\u0014", "" }, + { "\u0015", "" }, + { "\u0016", "" }, + { "\u0017", "" }, + { "\u0018", "" }, + { "\u0019", "" }, + { "\u001a", "" }, + { "\u001b", "" }, + { "\u001c", "" }, + { "\u001d", "" }, + { "\u001e", "" }, + { "\u001f", "" }, + { "\ufffe", "" }, + { "\uffff", "" } + }), + NumericEntityEscaper.between(0x7f, 0x84), + NumericEntityEscaper.between(0x86, 0x9f), + new UnicodeUnpairedSurrogateRemover() + ); + + /** + * Translator object for escaping XML 1.1. + * + * While {@link #escapeXml11(String)} is the expected method of use, this + * object allows the XML escaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.3 + */ + public static final CharSequenceTranslator ESCAPE_XML11 = + new AggregateTranslator( + new LookupTranslator(EntityArrays.BASIC_ESCAPE()), + new LookupTranslator(EntityArrays.APOS_ESCAPE()), + new LookupTranslator( + new String[][] { + { "\u0000", "" }, + { "\u000b", " " }, + { "\u000c", " " }, + { "\ufffe", "" }, + { "\uffff", "" } + }), + NumericEntityEscaper.between(0x1, 0x8), + NumericEntityEscaper.between(0xe, 0x1f), + NumericEntityEscaper.between(0x7f, 0x84), + NumericEntityEscaper.between(0x86, 0x9f), + new UnicodeUnpairedSurrogateRemover() + ); + + /** + * Translator object for escaping HTML version 3.0. + * + * While {@link #escapeHtml3(String)} is the expected method of use, this + * object allows the HTML escaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator ESCAPE_HTML3 = + new AggregateTranslator( + new LookupTranslator(EntityArrays.BASIC_ESCAPE()), + new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE()) + ); + + /** + * Translator object for escaping HTML version 4.0. + * + * While {@link #escapeHtml4(String)} is the expected method of use, this + * object allows the HTML escaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator ESCAPE_HTML4 = + new AggregateTranslator( + new LookupTranslator(EntityArrays.BASIC_ESCAPE()), + new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE()), + new LookupTranslator(EntityArrays.HTML40_EXTENDED_ESCAPE()) + ); + + /** + * Translator object for escaping individual Comma Separated Values. + * + * While {@link #escapeCsv(String)} is the expected method of use, this + * object allows the CSV escaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator ESCAPE_CSV = new CsvEscaper(); + + // TODO: Create a parent class - 'SinglePassTranslator' ? + // It would handle the index checking + length returning, + // and could also have an optimization check method. + static class CsvEscaper extends CharSequenceTranslator { + + private static final char CSV_DELIMITER = ','; + private static final char CSV_QUOTE = '"'; + private static final String CSV_QUOTE_STR = String.valueOf(CSV_QUOTE); + private static final char[] CSV_SEARCH_CHARS = + new char[] {CSV_DELIMITER, CSV_QUOTE, CharUtils.CR, CharUtils.LF}; + + @Override + public int translate(final CharSequence input, final int index, final Writer out) throws IOException { + + if(index != 0) { + throw new IllegalStateException("CsvEscaper should never reach the [1] index"); + } + + if (StringUtils.containsNone(input.toString(), CSV_SEARCH_CHARS)) { + out.write(input.toString()); + } else { + out.write(CSV_QUOTE); + out.write(StringUtils.replace(input.toString(), CSV_QUOTE_STR, CSV_QUOTE_STR + CSV_QUOTE_STR)); + out.write(CSV_QUOTE); + } + return Character.codePointCount(input, 0, input.length()); + } + } + + /* UNESCAPE TRANSLATORS */ + + /** + * Translator object for unescaping escaped Java. + * + * While {@link #unescapeJava(String)} is the expected method of use, this + * object allows the Java unescaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + // TODO: throw "illegal character: \92" as an Exception if a \ on the end of the Java (as per the compiler)? + public static final CharSequenceTranslator UNESCAPE_JAVA = + new AggregateTranslator( + new OctalUnescaper(), // .between('\1', '\377'), + new UnicodeUnescaper(), + new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_UNESCAPE()), + new LookupTranslator( + new String[][] { + {"\\\\", "\\"}, + {"\\\"", "\""}, + {"\\'", "'"}, + {"\\", ""} + }) + ); + + /** + * Translator object for unescaping escaped EcmaScript. + * + * While {@link #unescapeEcmaScript(String)} is the expected method of use, this + * object allows the EcmaScript unescaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator UNESCAPE_ECMASCRIPT = UNESCAPE_JAVA; + + /** + * Translator object for unescaping escaped Json. + * + * While {@link #unescapeJson(String)} is the expected method of use, this + * object allows the Json unescaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.2 + */ + public static final CharSequenceTranslator UNESCAPE_JSON = UNESCAPE_JAVA; + + /** + * Translator object for unescaping escaped HTML 3.0. + * + * While {@link #unescapeHtml3(String)} is the expected method of use, this + * object allows the HTML unescaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator UNESCAPE_HTML3 = + new AggregateTranslator( + new LookupTranslator(EntityArrays.BASIC_UNESCAPE()), + new LookupTranslator(EntityArrays.ISO8859_1_UNESCAPE()), + new NumericEntityUnescaper() + ); + + /** + * Translator object for unescaping escaped HTML 4.0. + * + * While {@link #unescapeHtml4(String)} is the expected method of use, this + * object allows the HTML unescaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator UNESCAPE_HTML4 = + new AggregateTranslator( + new LookupTranslator(EntityArrays.BASIC_UNESCAPE()), + new LookupTranslator(EntityArrays.ISO8859_1_UNESCAPE()), + new LookupTranslator(EntityArrays.HTML40_EXTENDED_UNESCAPE()), + new NumericEntityUnescaper() + ); + + /** + * Translator object for unescaping escaped XML. + * + * While {@link #unescapeXml(String)} is the expected method of use, this + * object allows the XML unescaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator UNESCAPE_XML = + new AggregateTranslator( + new LookupTranslator(EntityArrays.BASIC_UNESCAPE()), + new LookupTranslator(EntityArrays.APOS_UNESCAPE()), + new NumericEntityUnescaper() + ); + + /** + * Translator object for unescaping escaped Comma Separated Value entries. + * + * While {@link #unescapeCsv(String)} is the expected method of use, this + * object allows the CSV unescaping functionality to be used + * as the foundation for a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator UNESCAPE_CSV = new CsvUnescaper(); + + static class CsvUnescaper extends CharSequenceTranslator { + + private static final char CSV_DELIMITER = ','; + private static final char CSV_QUOTE = '"'; + private static final String CSV_QUOTE_STR = String.valueOf(CSV_QUOTE); + private static final char[] CSV_SEARCH_CHARS = + new char[] {CSV_DELIMITER, CSV_QUOTE, CharUtils.CR, CharUtils.LF}; + + @Override + public int translate(final CharSequence input, final int index, final Writer out) throws IOException { + + if(index != 0) { + throw new IllegalStateException("CsvUnescaper should never reach the [1] index"); + } + + if ( input.charAt(0) != CSV_QUOTE || input.charAt(input.length() - 1) != CSV_QUOTE ) { + out.write(input.toString()); + return Character.codePointCount(input, 0, input.length()); + } + + // strip quotes + final String quoteless = input.subSequence(1, input.length() - 1).toString(); + + if ( StringUtils.containsAny(quoteless, CSV_SEARCH_CHARS) ) { + // deal with escaped quotes; ie) "" + out.write(StringUtils.replace(quoteless, CSV_QUOTE_STR + CSV_QUOTE_STR, CSV_QUOTE_STR)); + } else { + out.write(input.toString()); + } + return Character.codePointCount(input, 0, input.length()); + } + } + + /* Helper functions */ + + /** + *

              {@code StringEscapeUtils} instances should NOT be constructed in + * standard programming.

              + * + *

              Instead, the class should be used as:

              + *
              StringEscapeUtils.escapeJava("foo");
              + * + *

              This constructor is public to permit tools that require a JavaBean + * instance to operate.

              + */ + public StringEscapeUtils() { + super(); + } + + // Java and JavaScript + //-------------------------------------------------------------------------- + /** + *

              Escapes the characters in a {@code String} using Java String rules.

              + * + *

              Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)

              + * + *

              So a tab becomes the characters {@code '\\'} and + * {@code 't'}.

              + * + *

              The only difference between Java strings and JavaScript strings + * is that in JavaScript, a single quote and forward-slash (/) are escaped.

              + * + *

              Example:

              + *
              +     * input string: He didn't say, "Stop!"
              +     * output string: He didn't say, \"Stop!\"
              +     * 
              + * + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input + */ + public static final String escapeJava(final String input) { + return ESCAPE_JAVA.translate(input); + } + + /** + *

              Escapes the characters in a {@code String} using EcmaScript String rules.

              + *

              Escapes any values it finds into their EcmaScript String form. + * Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)

              + * + *

              So a tab becomes the characters {@code '\\'} and + * {@code 't'}.

              + * + *

              The only difference between Java strings and EcmaScript strings + * is that in EcmaScript, a single quote and forward-slash (/) are escaped.

              + * + *

              Note that EcmaScript is best known by the JavaScript and ActionScript dialects.

              + * + *

              Example:

              + *
              +     * input string: He didn't say, "Stop!"
              +     * output string: He didn\'t say, \"Stop!\"
              +     * 
              + * + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input + * + * @since 3.0 + */ + public static final String escapeEcmaScript(final String input) { + return ESCAPE_ECMASCRIPT.translate(input); + } + + /** + *

              Escapes the characters in a {@code String} using Json String rules.

              + *

              Escapes any values it finds into their Json String form. + * Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)

              + * + *

              So a tab becomes the characters {@code '\\'} and + * {@code 't'}.

              + * + *

              The only difference between Java strings and Json strings + * is that in Json, forward-slash (/) is escaped.

              + * + *

              See http://www.ietf.org/rfc/rfc4627.txt for further details.

              + * + *

              Example:

              + *
              +     * input string: He didn't say, "Stop!"
              +     * output string: He didn't say, \"Stop!\"
              +     * 
              + * + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input + * + * @since 3.2 + */ + public static final String escapeJson(final String input) { + return ESCAPE_JSON.translate(input); + } + + /** + *

              Unescapes any Java literals found in the {@code String}. + * For example, it will turn a sequence of {@code '\'} and + * {@code 'n'} into a newline character, unless the {@code '\'} + * is preceded by another {@code '\'}.

              + * + * @param input the {@code String} to unescape, may be null + * @return a new unescaped {@code String}, {@code null} if null string input + */ + public static final String unescapeJava(final String input) { + return UNESCAPE_JAVA.translate(input); + } + + /** + *

              Unescapes any EcmaScript literals found in the {@code String}.

              + * + *

              For example, it will turn a sequence of {@code '\'} and {@code 'n'} + * into a newline character, unless the {@code '\'} is preceded by another + * {@code '\'}.

              + * + * @see #unescapeJava(String) + * @param input the {@code String} to unescape, may be null + * @return A new unescaped {@code String}, {@code null} if null string input + * + * @since 3.0 + */ + public static final String unescapeEcmaScript(final String input) { + return UNESCAPE_ECMASCRIPT.translate(input); + } + + /** + *

              Unescapes any Json literals found in the {@code String}.

              + * + *

              For example, it will turn a sequence of {@code '\'} and {@code 'n'} + * into a newline character, unless the {@code '\'} is preceded by another + * {@code '\'}.

              + * + * @see #unescapeJava(String) + * @param input the {@code String} to unescape, may be null + * @return A new unescaped {@code String}, {@code null} if null string input + * + * @since 3.2 + */ + public static final String unescapeJson(final String input) { + return UNESCAPE_JSON.translate(input); + } + + // HTML and XML + //-------------------------------------------------------------------------- + /** + *

              Escapes the characters in a {@code String} using HTML entities.

              + * + *

              + * For example: + *

              + *

              "bread" & "butter"

              + * becomes: + *

              + * &quot;bread&quot; &amp; &quot;butter&quot;. + *

              + * + *

              Supports all known HTML 4.0 entities, including funky accents. + * Note that the commonly used apostrophe escape character (&apos;) + * is not a legal entity and so is not supported).

              + * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * + * @see ISO Entities + * @see HTML 3.2 Character Entities for ISO Latin-1 + * @see HTML 4.0 Character entity references + * @see HTML 4.01 Character References + * @see HTML 4.01 Code positions + * + * @since 3.0 + */ + public static final String escapeHtml4(final String input) { + return ESCAPE_HTML4.translate(input); + } + + /** + *

              Escapes the characters in a {@code String} using HTML entities.

              + *

              Supports only the HTML 3.0 entities.

              + * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * + * @since 3.0 + */ + public static final String escapeHtml3(final String input) { + return ESCAPE_HTML3.translate(input); + } + + //----------------------------------------------------------------------- + /** + *

              Unescapes a string containing entity escapes to a string + * containing the actual Unicode characters corresponding to the + * escapes. Supports HTML 4.0 entities.

              + * + *

              For example, the string {@code "<Français>"} + * will become {@code ""}

              + * + *

              If an entity is unrecognized, it is left alone, and inserted + * verbatim into the result string. e.g. {@code ">&zzzz;x"} will + * become {@code ">&zzzz;x"}.

              + * + * @param input the {@code String} to unescape, may be null + * @return a new unescaped {@code String}, {@code null} if null string input + * + * @since 3.0 + */ + public static final String unescapeHtml4(final String input) { + return UNESCAPE_HTML4.translate(input); + } + + /** + *

              Unescapes a string containing entity escapes to a string + * containing the actual Unicode characters corresponding to the + * escapes. Supports only HTML 3.0 entities.

              + * + * @param input the {@code String} to unescape, may be null + * @return a new unescaped {@code String}, {@code null} if null string input + * + * @since 3.0 + */ + public static final String unescapeHtml3(final String input) { + return UNESCAPE_HTML3.translate(input); + } + + //----------------------------------------------------------------------- + /** + *

              Escapes the characters in a {@code String} using XML entities.

              + * + *

              For example: {@code "bread" & "butter"} => + * {@code "bread" & "butter"}. + *

              + * + *

              Supports only the five basic XML entities (gt, lt, quot, amp, apos). + * Does not support DTDs or external entities.

              + * + *

              Note that Unicode characters greater than 0x7f are as of 3.0, no longer + * escaped. If you still wish this functionality, you can achieve it + * via the following: + * {@code StringEscapeUtils.ESCAPE_XML.with( NumericEntityEscaper.between(0x7f, Integer.MAX_VALUE) );}

              + * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * @see #unescapeXml(java.lang.String) + * @deprecated use {@link #escapeXml10(java.lang.String)} or {@link #escapeXml11(java.lang.String)} instead. + */ + @Deprecated + public static final String escapeXml(final String input) { + return ESCAPE_XML.translate(input); + } + + /** + *

              Escapes the characters in a {@code String} using XML entities.

              + * + *

              For example: {@code "bread" & "butter"} => + * {@code "bread" & "butter"}. + *

              + * + *

              Note that XML 1.0 is a text-only format: it cannot represent control + * characters or unpaired Unicode surrogate codepoints, even after escaping. + * {@code escapeXml10} will remove characters that do not fit in the + * following ranges:

              + * + *

              {@code #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]}

              + * + *

              Though not strictly necessary, {@code escapeXml10} will escape + * characters in the following ranges:

              + * + *

              {@code [#x7F-#x84] | [#x86-#x9F]}

              + * + *

              The returned string can be inserted into a valid XML 1.0 or XML 1.1 + * document. If you want to allow more non-text characters in an XML 1.1 + * document, use {@link #escapeXml11(String)}.

              + * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * @see #unescapeXml(java.lang.String) + * @since 3.3 + */ + public static String escapeXml10(final String input) { + return ESCAPE_XML10.translate(input); + } + + /** + *

              Escapes the characters in a {@code String} using XML entities.

              + * + *

              For example: {@code "bread" & "butter"} => + * {@code "bread" & "butter"}. + *

              + * + *

              XML 1.1 can represent certain control characters, but it cannot represent + * the null byte or unpaired Unicode surrogate codepoints, even after escaping. + * {@code escapeXml11} will remove characters that do not fit in the following + * ranges:

              + * + *

              {@code [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]}

              + * + *

              {@code escapeXml11} will escape characters in the following ranges:

              + * + *

              {@code [#x1-#x8] | [#xB-#xC] | [#xE-#x1F] | [#x7F-#x84] | [#x86-#x9F]}

              + * + *

              The returned string can be inserted into a valid XML 1.1 document. Do not + * use it for XML 1.0 documents.

              + * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * @see #unescapeXml(java.lang.String) + * @since 3.3 + */ + public static String escapeXml11(final String input) { + return ESCAPE_XML11.translate(input); + } + + //----------------------------------------------------------------------- + /** + *

              Unescapes a string containing XML entity escapes to a string + * containing the actual Unicode characters corresponding to the + * escapes.

              + * + *

              Supports only the five basic XML entities (gt, lt, quot, amp, apos). + * Does not support DTDs or external entities.

              + * + *

              Note that numerical \\u Unicode codes are unescaped to their respective + * Unicode characters. This may change in future releases.

              + * + * @param input the {@code String} to unescape, may be null + * @return a new unescaped {@code String}, {@code null} if null string input + * @see #escapeXml(String) + * @see #escapeXml10(String) + * @see #escapeXml11(String) + */ + public static final String unescapeXml(final String input) { + return UNESCAPE_XML.translate(input); + } + + //----------------------------------------------------------------------- + + /** + *

              Returns a {@code String} value for a CSV column enclosed in double quotes, + * if required.

              + * + *

              If the value contains a comma, newline or double quote, then the + * String value is returned enclosed in double quotes.

              + * + *

              Any double quote characters in the value are escaped with another double quote.

              + * + *

              If the value does not contain a comma, newline or double quote, then the + * String value is returned unchanged.

              + * + * see Wikipedia and + * RFC 4180. + * + * @param input the input CSV column String, may be null + * @return the input String, enclosed in double quotes if the value contains a comma, + * newline or double quote, {@code null} if null string input + * @since 2.4 + */ + public static final String escapeCsv(final String input) { + return ESCAPE_CSV.translate(input); + } + + /** + *

              Returns a {@code String} value for an unescaped CSV column.

              + * + *

              If the value is enclosed in double quotes, and contains a comma, newline + * or double quote, then quotes are removed. + *

              + * + *

              Any double quote escaped characters (a pair of double quotes) are unescaped + * to just one double quote.

              + * + *

              If the value is not enclosed in double quotes, or is and does not contain a + * comma, newline or double quote, then the String value is returned unchanged.

              + * + * see Wikipedia and + * RFC 4180. + * + * @param input the input CSV column String, may be null + * @return the input String, with enclosing double quotes removed and embedded double + * quotes unescaped, {@code null} if null string input + * @since 2.4 + */ + public static final String unescapeCsv(final String input) { + return UNESCAPE_CSV.translate(input); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/StringUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/StringUtils.java new file mode 100644 index 000000000..311a44814 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/StringUtils.java @@ -0,0 +1,7879 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import com.fr.third.org.apache.commons.lang3.text.WordUtils; + +import java.io.UnsupportedEncodingException; + +import java.nio.charset.Charset; +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + +/** + *

              Operations on {@link java.lang.String} that are + * {@code null} safe.

              + * + *
                + *
              • IsEmpty/IsBlank + * - checks if a String contains text
              • + *
              • Trim/Strip + * - removes leading and trailing whitespace
              • + *
              • Equals + * - compares two strings null-safe
              • + *
              • startsWith + * - check if a String starts with a prefix null-safe
              • + *
              • endsWith + * - check if a String ends with a suffix null-safe
              • + *
              • IndexOf/LastIndexOf/Contains + * - null-safe index-of checks + *
              • IndexOfAny/LastIndexOfAny/IndexOfAnyBut/LastIndexOfAnyBut + * - index-of any of a set of Strings
              • + *
              • ContainsOnly/ContainsNone/ContainsAny + * - does String contains only/none/any of these characters
              • + *
              • Substring/Left/Right/Mid + * - null-safe substring extractions
              • + *
              • SubstringBefore/SubstringAfter/SubstringBetween + * - substring extraction relative to other strings
              • + *
              • Split/Join + * - splits a String into an array of substrings and vice versa
              • + *
              • Remove/Delete + * - removes part of a String
              • + *
              • Replace/Overlay + * - Searches a String and replaces one String with another
              • + *
              • Chomp/Chop + * - removes the last part of a String
              • + *
              • AppendIfMissing + * - appends a suffix to the end of the String if not present
              • + *
              • PrependIfMissing + * - prepends a prefix to the start of the String if not present
              • + *
              • LeftPad/RightPad/Center/Repeat + * - pads a String
              • + *
              • UpperCase/LowerCase/SwapCase/Capitalize/Uncapitalize + * - changes the case of a String
              • + *
              • CountMatches + * - counts the number of occurrences of one String in another
              • + *
              • IsAlpha/IsNumeric/IsWhitespace/IsAsciiPrintable + * - checks the characters in a String
              • + *
              • DefaultString + * - protects against a null input String
              • + *
              • Reverse/ReverseDelimited + * - reverses a String
              • + *
              • Abbreviate + * - abbreviates a string using ellipsis
              • + *
              • Difference + * - compares Strings and reports on their differences
              • + *
              • LevenshteinDistance + * - the number of changes needed to change one String into another
              • + *
              + * + *

              The {@code StringUtils} class defines certain words related to + * String handling.

              + * + *
                + *
              • null - {@code null}
              • + *
              • empty - a zero-length string ({@code ""})
              • + *
              • space - the space character ({@code ' '}, char 32)
              • + *
              • whitespace - the characters defined by {@link Character#isWhitespace(char)}
              • + *
              • trim - the characters <= 32 as in {@link String#trim()}
              • + *
              + * + *

              {@code StringUtils} handles {@code null} input Strings quietly. + * That is to say that a {@code null} input will return {@code null}. + * Where a {@code boolean} or {@code int} is being returned + * details vary by method.

              + * + *

              A side effect of the {@code null} handling is that a + * {@code NullPointerException} should be considered a bug in + * {@code StringUtils}.

              + * + *

              Methods in this class give sample code to explain their operation. + * The symbol {@code *} is used to indicate any input including {@code null}.

              + * + *

              #ThreadSafe#

              + * @see java.lang.String + * @since 1.0 + * @version $Id: StringUtils.java 1648067 2014-12-27 16:45:42Z britter $ + */ +//@Immutable +public class StringUtils { + // Performance testing notes (JDK 1.4, Jul03, scolebourne) + // Whitespace: + // Character.isWhitespace() is faster than WHITESPACE.indexOf() + // where WHITESPACE is a string of all whitespace characters + // + // Character access: + // String.charAt(n) versus toCharArray(), then array[n] + // String.charAt(n) is about 15% worse for a 10K string + // They are about equal for a length 50 string + // String.charAt(n) is about 4 times better for a length 3 string + // String.charAt(n) is best bet overall + // + // Append: + // String.concat about twice as fast as StringBuffer.append + // (not sure who tested this) + + /** + * A String for a space character. + * + * @since 3.2 + */ + public static final String SPACE = " "; + + /** + * The empty String {@code ""}. + * @since 2.0 + */ + public static final String EMPTY = ""; + + /** + * A String for linefeed LF ("\n"). + * + * @see JLF: Escape Sequences + * for Character and String Literals + * @since 3.2 + */ + public static final String LF = "\n"; + + /** + * A String for carriage return CR ("\r"). + * + * @see JLF: Escape Sequences + * for Character and String Literals + * @since 3.2 + */ + public static final String CR = "\r"; + + /** + * Represents a failed index search. + * @since 2.1 + */ + public static final int INDEX_NOT_FOUND = -1; + + /** + *

              The maximum size to which the padding constant(s) can expand.

              + */ + private static final int PAD_LIMIT = 8192; + + /** + *

              {@code StringUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used as + * {@code StringUtils.trim(" foo ");}.

              + * + *

              This constructor is public to permit tools that require a JavaBean + * instance to operate.

              + */ + public StringUtils() { + super(); + } + + // Empty checks + //----------------------------------------------------------------------- + /** + *

              Checks if a CharSequence is empty ("") or null.

              + * + *
              +     * StringUtils.isEmpty(null)      = true
              +     * StringUtils.isEmpty("")        = true
              +     * StringUtils.isEmpty(" ")       = false
              +     * StringUtils.isEmpty("bob")     = false
              +     * StringUtils.isEmpty("  bob  ") = false
              +     * 
              + * + *

              NOTE: This method changed in Lang version 2.0. + * It no longer trims the CharSequence. + * That functionality is available in isBlank().

              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is empty or null + * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence) + */ + public static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } + + /** + *

              Checks if a CharSequence is not empty ("") and not null.

              + * + *
              +     * StringUtils.isNotEmpty(null)      = false
              +     * StringUtils.isNotEmpty("")        = false
              +     * StringUtils.isNotEmpty(" ")       = true
              +     * StringUtils.isNotEmpty("bob")     = true
              +     * StringUtils.isNotEmpty("  bob  ") = true
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is not empty and not null + * @since 3.0 Changed signature from isNotEmpty(String) to isNotEmpty(CharSequence) + */ + public static boolean isNotEmpty(final CharSequence cs) { + return !isEmpty(cs); + } + + /** + *

              Checks if any one of the CharSequences are empty ("") or null.

              + * + *
              +     * StringUtils.isAnyEmpty(null)             = true
              +     * StringUtils.isAnyEmpty(null, "foo")      = true
              +     * StringUtils.isAnyEmpty("", "bar")        = true
              +     * StringUtils.isAnyEmpty("bob", "")        = true
              +     * StringUtils.isAnyEmpty("  bob  ", null)  = true
              +     * StringUtils.isAnyEmpty(" ", "bar")       = false
              +     * StringUtils.isAnyEmpty("foo", "bar")     = false
              +     * 
              + * + * @param css the CharSequences to check, may be null or empty + * @return {@code true} if any of the CharSequences are empty or null + * @since 3.2 + */ + public static boolean isAnyEmpty(final CharSequence... css) { + if (ArrayUtils.isEmpty(css)) { + return true; + } + for (final CharSequence cs : css){ + if (isEmpty(cs)) { + return true; + } + } + return false; + } + + /** + *

              Checks if none of the CharSequences are empty ("") or null.

              + * + *
              +     * StringUtils.isNoneEmpty(null)             = false
              +     * StringUtils.isNoneEmpty(null, "foo")      = false
              +     * StringUtils.isNoneEmpty("", "bar")        = false
              +     * StringUtils.isNoneEmpty("bob", "")        = false
              +     * StringUtils.isNoneEmpty("  bob  ", null)  = false
              +     * StringUtils.isNoneEmpty(" ", "bar")       = true
              +     * StringUtils.isNoneEmpty("foo", "bar")     = true
              +     * 
              + * + * @param css the CharSequences to check, may be null or empty + * @return {@code true} if none of the CharSequences are empty or null + * @since 3.2 + */ + public static boolean isNoneEmpty(final CharSequence... css) { + return !isAnyEmpty(css); + } + /** + *

              Checks if a CharSequence is whitespace, empty ("") or null.

              + * + *
              +     * StringUtils.isBlank(null)      = true
              +     * StringUtils.isBlank("")        = true
              +     * StringUtils.isBlank(" ")       = true
              +     * StringUtils.isBlank("bob")     = false
              +     * StringUtils.isBlank("  bob  ") = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is null, empty or whitespace + * @since 2.0 + * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence) + */ + public static boolean isBlank(final CharSequence cs) { + int strLen; + if (cs == null || (strLen = cs.length()) == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if (Character.isWhitespace(cs.charAt(i)) == false) { + return false; + } + } + return true; + } + + /** + *

              Checks if a CharSequence is not empty (""), not null and not whitespace only.

              + * + *
              +     * StringUtils.isNotBlank(null)      = false
              +     * StringUtils.isNotBlank("")        = false
              +     * StringUtils.isNotBlank(" ")       = false
              +     * StringUtils.isNotBlank("bob")     = true
              +     * StringUtils.isNotBlank("  bob  ") = true
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is + * not empty and not null and not whitespace + * @since 2.0 + * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) + */ + public static boolean isNotBlank(final CharSequence cs) { + return !isBlank(cs); + } + + /** + *

              Checks if any one of the CharSequences are blank ("") or null and not whitespace only..

              + * + *
              +     * StringUtils.isAnyBlank(null)             = true
              +     * StringUtils.isAnyBlank(null, "foo")      = true
              +     * StringUtils.isAnyBlank(null, null)       = true
              +     * StringUtils.isAnyBlank("", "bar")        = true
              +     * StringUtils.isAnyBlank("bob", "")        = true
              +     * StringUtils.isAnyBlank("  bob  ", null)  = true
              +     * StringUtils.isAnyBlank(" ", "bar")       = true
              +     * StringUtils.isAnyBlank("foo", "bar")     = false
              +     * 
              + * + * @param css the CharSequences to check, may be null or empty + * @return {@code true} if any of the CharSequences are blank or null or whitespace only + * @since 3.2 + */ + public static boolean isAnyBlank(final CharSequence... css) { + if (ArrayUtils.isEmpty(css)) { + return true; + } + for (final CharSequence cs : css){ + if (isBlank(cs)) { + return true; + } + } + return false; + } + + /** + *

              Checks if none of the CharSequences are blank ("") or null and whitespace only..

              + * + *
              +     * StringUtils.isNoneBlank(null)             = false
              +     * StringUtils.isNoneBlank(null, "foo")      = false
              +     * StringUtils.isNoneBlank(null, null)       = false
              +     * StringUtils.isNoneBlank("", "bar")        = false
              +     * StringUtils.isNoneBlank("bob", "")        = false
              +     * StringUtils.isNoneBlank("  bob  ", null)  = false
              +     * StringUtils.isNoneBlank(" ", "bar")       = false
              +     * StringUtils.isNoneBlank("foo", "bar")     = true
              +     * 
              + * + * @param css the CharSequences to check, may be null or empty + * @return {@code true} if none of the CharSequences are blank or null or whitespace only + * @since 3.2 + */ + public static boolean isNoneBlank(final CharSequence... css) { + return !isAnyBlank(css); + } + + // Trim + //----------------------------------------------------------------------- + /** + *

              Removes control characters (char <= 32) from both + * ends of this String, handling {@code null} by returning + * {@code null}.

              + * + *

              The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #strip(String)}.

              + * + *

              To trim your choice of characters, use the + * {@link #strip(String, String)} methods.

              + * + *
              +     * StringUtils.trim(null)          = null
              +     * StringUtils.trim("")            = ""
              +     * StringUtils.trim("     ")       = ""
              +     * StringUtils.trim("abc")         = "abc"
              +     * StringUtils.trim("    abc    ") = "abc"
              +     * 
              + * + * @param str the String to be trimmed, may be null + * @return the trimmed string, {@code null} if null String input + */ + public static String trim(final String str) { + return str == null ? null : str.trim(); + } + + /** + *

              Removes control characters (char <= 32) from both + * ends of this String returning {@code null} if the String is + * empty ("") after the trim or if it is {@code null}. + * + *

              The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #stripToNull(String)}.

              + * + *
              +     * StringUtils.trimToNull(null)          = null
              +     * StringUtils.trimToNull("")            = null
              +     * StringUtils.trimToNull("     ")       = null
              +     * StringUtils.trimToNull("abc")         = "abc"
              +     * StringUtils.trimToNull("    abc    ") = "abc"
              +     * 
              + * + * @param str the String to be trimmed, may be null + * @return the trimmed String, + * {@code null} if only chars <= 32, empty or null String input + * @since 2.0 + */ + public static String trimToNull(final String str) { + final String ts = trim(str); + return isEmpty(ts) ? null : ts; + } + + /** + *

              Removes control characters (char <= 32) from both + * ends of this String returning an empty String ("") if the String + * is empty ("") after the trim or if it is {@code null}. + * + *

              The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #stripToEmpty(String)}.

              + * + *
              +     * StringUtils.trimToEmpty(null)          = ""
              +     * StringUtils.trimToEmpty("")            = ""
              +     * StringUtils.trimToEmpty("     ")       = ""
              +     * StringUtils.trimToEmpty("abc")         = "abc"
              +     * StringUtils.trimToEmpty("    abc    ") = "abc"
              +     * 
              + * + * @param str the String to be trimmed, may be null + * @return the trimmed String, or an empty String if {@code null} input + * @since 2.0 + */ + public static String trimToEmpty(final String str) { + return str == null ? EMPTY : str.trim(); + } + + // Stripping + //----------------------------------------------------------------------- + /** + *

              Strips whitespace from the start and end of a String.

              + * + *

              This is similar to {@link #trim(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

              + * + *

              A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.strip(null)     = null
              +     * StringUtils.strip("")       = ""
              +     * StringUtils.strip("   ")    = ""
              +     * StringUtils.strip("abc")    = "abc"
              +     * StringUtils.strip("  abc")  = "abc"
              +     * StringUtils.strip("abc  ")  = "abc"
              +     * StringUtils.strip(" abc ")  = "abc"
              +     * StringUtils.strip(" ab c ") = "ab c"
              +     * 
              + * + * @param str the String to remove whitespace from, may be null + * @return the stripped String, {@code null} if null String input + */ + public static String strip(final String str) { + return strip(str, null); + } + + /** + *

              Strips whitespace from the start and end of a String returning + * {@code null} if the String is empty ("") after the strip.

              + * + *

              This is similar to {@link #trimToNull(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

              + * + *
              +     * StringUtils.stripToNull(null)     = null
              +     * StringUtils.stripToNull("")       = null
              +     * StringUtils.stripToNull("   ")    = null
              +     * StringUtils.stripToNull("abc")    = "abc"
              +     * StringUtils.stripToNull("  abc")  = "abc"
              +     * StringUtils.stripToNull("abc  ")  = "abc"
              +     * StringUtils.stripToNull(" abc ")  = "abc"
              +     * StringUtils.stripToNull(" ab c ") = "ab c"
              +     * 
              + * + * @param str the String to be stripped, may be null + * @return the stripped String, + * {@code null} if whitespace, empty or null String input + * @since 2.0 + */ + public static String stripToNull(String str) { + if (str == null) { + return null; + } + str = strip(str, null); + return str.isEmpty() ? null : str; + } + + /** + *

              Strips whitespace from the start and end of a String returning + * an empty String if {@code null} input.

              + * + *

              This is similar to {@link #trimToEmpty(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

              + * + *
              +     * StringUtils.stripToEmpty(null)     = ""
              +     * StringUtils.stripToEmpty("")       = ""
              +     * StringUtils.stripToEmpty("   ")    = ""
              +     * StringUtils.stripToEmpty("abc")    = "abc"
              +     * StringUtils.stripToEmpty("  abc")  = "abc"
              +     * StringUtils.stripToEmpty("abc  ")  = "abc"
              +     * StringUtils.stripToEmpty(" abc ")  = "abc"
              +     * StringUtils.stripToEmpty(" ab c ") = "ab c"
              +     * 
              + * + * @param str the String to be stripped, may be null + * @return the trimmed String, or an empty String if {@code null} input + * @since 2.0 + */ + public static String stripToEmpty(final String str) { + return str == null ? EMPTY : strip(str, null); + } + + /** + *

              Strips any of a set of characters from the start and end of a String. + * This is similar to {@link String#trim()} but allows the characters + * to be stripped to be controlled.

              + * + *

              A {@code null} input String returns {@code null}. + * An empty string ("") input returns the empty string.

              + * + *

              If the stripChars String is {@code null}, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}. + * Alternatively use {@link #strip(String)}.

              + * + *
              +     * StringUtils.strip(null, *)          = null
              +     * StringUtils.strip("", *)            = ""
              +     * StringUtils.strip("abc", null)      = "abc"
              +     * StringUtils.strip("  abc", null)    = "abc"
              +     * StringUtils.strip("abc  ", null)    = "abc"
              +     * StringUtils.strip(" abc ", null)    = "abc"
              +     * StringUtils.strip("  abcyx", "xyz") = "  abc"
              +     * 
              + * + * @param str the String to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped String, {@code null} if null String input + */ + public static String strip(String str, final String stripChars) { + if (isEmpty(str)) { + return str; + } + str = stripStart(str, stripChars); + return stripEnd(str, stripChars); + } + + /** + *

              Strips any of a set of characters from the start of a String.

              + * + *

              A {@code null} input String returns {@code null}. + * An empty string ("") input returns the empty string.

              + * + *

              If the stripChars String is {@code null}, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}.

              + * + *
              +     * StringUtils.stripStart(null, *)          = null
              +     * StringUtils.stripStart("", *)            = ""
              +     * StringUtils.stripStart("abc", "")        = "abc"
              +     * StringUtils.stripStart("abc", null)      = "abc"
              +     * StringUtils.stripStart("  abc", null)    = "abc"
              +     * StringUtils.stripStart("abc  ", null)    = "abc  "
              +     * StringUtils.stripStart(" abc ", null)    = "abc "
              +     * StringUtils.stripStart("yxabc  ", "xyz") = "abc  "
              +     * 
              + * + * @param str the String to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped String, {@code null} if null String input + */ + public static String stripStart(final String str, final String stripChars) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return str; + } + int start = 0; + if (stripChars == null) { + while (start != strLen && Character.isWhitespace(str.charAt(start))) { + start++; + } + } else if (stripChars.isEmpty()) { + return str; + } else { + while (start != strLen && stripChars.indexOf(str.charAt(start)) != INDEX_NOT_FOUND) { + start++; + } + } + return str.substring(start); + } + + /** + *

              Strips any of a set of characters from the end of a String.

              + * + *

              A {@code null} input String returns {@code null}. + * An empty string ("") input returns the empty string.

              + * + *

              If the stripChars String is {@code null}, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}.

              + * + *
              +     * StringUtils.stripEnd(null, *)          = null
              +     * StringUtils.stripEnd("", *)            = ""
              +     * StringUtils.stripEnd("abc", "")        = "abc"
              +     * StringUtils.stripEnd("abc", null)      = "abc"
              +     * StringUtils.stripEnd("  abc", null)    = "  abc"
              +     * StringUtils.stripEnd("abc  ", null)    = "abc"
              +     * StringUtils.stripEnd(" abc ", null)    = " abc"
              +     * StringUtils.stripEnd("  abcyx", "xyz") = "  abc"
              +     * StringUtils.stripEnd("120.00", ".0")   = "12"
              +     * 
              + * + * @param str the String to remove characters from, may be null + * @param stripChars the set of characters to remove, null treated as whitespace + * @return the stripped String, {@code null} if null String input + */ + public static String stripEnd(final String str, final String stripChars) { + int end; + if (str == null || (end = str.length()) == 0) { + return str; + } + + if (stripChars == null) { + while (end != 0 && Character.isWhitespace(str.charAt(end - 1))) { + end--; + } + } else if (stripChars.isEmpty()) { + return str; + } else { + while (end != 0 && stripChars.indexOf(str.charAt(end - 1)) != INDEX_NOT_FOUND) { + end--; + } + } + return str.substring(0, end); + } + + // StripAll + //----------------------------------------------------------------------- + /** + *

              Strips whitespace from the start and end of every String in an array. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

              + * + *

              A new array is returned each time, except for length zero. + * A {@code null} array will return {@code null}. + * An empty array will return itself. + * A {@code null} array entry will be ignored.

              + * + *
              +     * StringUtils.stripAll(null)             = null
              +     * StringUtils.stripAll([])               = []
              +     * StringUtils.stripAll(["abc", "  abc"]) = ["abc", "abc"]
              +     * StringUtils.stripAll(["abc  ", null])  = ["abc", null]
              +     * 
              + * + * @param strs the array to remove whitespace from, may be null + * @return the stripped Strings, {@code null} if null array input + */ + public static String[] stripAll(final String... strs) { + return stripAll(strs, null); + } + + /** + *

              Strips any of a set of characters from the start and end of every + * String in an array.

              + *

              Whitespace is defined by {@link Character#isWhitespace(char)}.

              + * + *

              A new array is returned each time, except for length zero. + * A {@code null} array will return {@code null}. + * An empty array will return itself. + * A {@code null} array entry will be ignored. + * A {@code null} stripChars will strip whitespace as defined by + * {@link Character#isWhitespace(char)}.

              + * + *
              +     * StringUtils.stripAll(null, *)                = null
              +     * StringUtils.stripAll([], *)                  = []
              +     * StringUtils.stripAll(["abc", "  abc"], null) = ["abc", "abc"]
              +     * StringUtils.stripAll(["abc  ", null], null)  = ["abc", null]
              +     * StringUtils.stripAll(["abc  ", null], "yz")  = ["abc  ", null]
              +     * StringUtils.stripAll(["yabcz", null], "yz")  = ["abc", null]
              +     * 
              + * + * @param strs the array to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped Strings, {@code null} if null array input + */ + public static String[] stripAll(final String[] strs, final String stripChars) { + int strsLen; + if (strs == null || (strsLen = strs.length) == 0) { + return strs; + } + final String[] newArr = new String[strsLen]; + for (int i = 0; i < strsLen; i++) { + newArr[i] = strip(strs[i], stripChars); + } + return newArr; + } + + /** + *

              Removes diacritics (~= accents) from a string. The case will not be altered.

              + *

              For instance, 'à' will be replaced by 'a'.

              + *

              Note that ligatures will be left as is.

              + * + *
              +     * StringUtils.stripAccents(null)                = null
              +     * StringUtils.stripAccents("")                  = ""
              +     * StringUtils.stripAccents("control")           = "control"
              +     * StringUtils.stripAccents("éclair")     = "eclair"
              +     * 
              + * + * @param input String to be stripped + * @return input text with diacritics removed + * + * @since 3.0 + */ + // See also Lucene's ASCIIFoldingFilter (Lucene 2.9) that replaces accented characters by their unaccented equivalent (and uncommitted bug fix: https://issues.apache.org/jira/browse/LUCENE-1343?focusedCommentId=12858907&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_12858907). + public static String stripAccents(final String input) { + if(input == null) { + return null; + } + final Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");//$NON-NLS-1$ + final String decomposed = Normalizer.normalize(input, Normalizer.Form.NFD); + // Note that this doesn't correctly remove ligatures... + return pattern.matcher(decomposed).replaceAll("");//$NON-NLS-1$ + } + + // Equals + //----------------------------------------------------------------------- + /** + *

              Compares two CharSequences, returning {@code true} if they represent + * equal sequences of characters.

              + * + *

              {@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

              + * + *
              +     * StringUtils.equals(null, null)   = true
              +     * StringUtils.equals(null, "abc")  = false
              +     * StringUtils.equals("abc", null)  = false
              +     * StringUtils.equals("abc", "abc") = true
              +     * StringUtils.equals("abc", "ABC") = false
              +     * 
              + * + * @see Object#equals(Object) + * @param cs1 the first CharSequence, may be {@code null} + * @param cs2 the second CharSequence, may be {@code null} + * @return {@code true} if the CharSequences are equal (case-sensitive), or both {@code null} + * @since 3.0 Changed signature from equals(String, String) to equals(CharSequence, CharSequence) + */ + public static boolean equals(final CharSequence cs1, final CharSequence cs2) { + if (cs1 == cs2) { + return true; + } + if (cs1 == null || cs2 == null) { + return false; + } + if (cs1 instanceof String && cs2 instanceof String) { + return cs1.equals(cs2); + } + return CharSequenceUtils.regionMatches(cs1, false, 0, cs2, 0, Math.max(cs1.length(), cs2.length())); + } + + /** + *

              Compares two CharSequences, returning {@code true} if they represent + * equal sequences of characters, ignoring case.

              + * + *

              {@code null}s are handled without exceptions. Two {@code null} + * references are considered equal. Comparison is case insensitive.

              + * + *
              +     * StringUtils.equalsIgnoreCase(null, null)   = true
              +     * StringUtils.equalsIgnoreCase(null, "abc")  = false
              +     * StringUtils.equalsIgnoreCase("abc", null)  = false
              +     * StringUtils.equalsIgnoreCase("abc", "abc") = true
              +     * StringUtils.equalsIgnoreCase("abc", "ABC") = true
              +     * 
              + * + * @param str1 the first CharSequence, may be null + * @param str2 the second CharSequence, may be null + * @return {@code true} if the CharSequence are equal, case insensitive, or + * both {@code null} + * @since 3.0 Changed signature from equalsIgnoreCase(String, String) to equalsIgnoreCase(CharSequence, CharSequence) + */ + public static boolean equalsIgnoreCase(final CharSequence str1, final CharSequence str2) { + if (str1 == null || str2 == null) { + return str1 == str2; + } else if (str1 == str2) { + return true; + } else if (str1.length() != str2.length()) { + return false; + } else { + return CharSequenceUtils.regionMatches(str1, true, 0, str2, 0, str1.length()); + } + } + + // IndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the first index within a CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(int, int)} if possible.

              + * + *

              A {@code null} or empty ("") CharSequence will return {@code INDEX_NOT_FOUND (-1)}.

              + * + *
              +     * StringUtils.indexOf(null, *)         = -1
              +     * StringUtils.indexOf("", *)           = -1
              +     * StringUtils.indexOf("aabaabaa", 'a') = 0
              +     * StringUtils.indexOf("aabaabaa", 'b') = 2
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchChar the character to find + * @return the first index of the search character, + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from indexOf(String, int) to indexOf(CharSequence, int) + */ + public static int indexOf(final CharSequence seq, final int searchChar) { + if (isEmpty(seq)) { + return INDEX_NOT_FOUND; + } + return CharSequenceUtils.indexOf(seq, searchChar, 0); + } + + /** + *

              Finds the first index within a CharSequence from a start position, + * handling {@code null}. + * This method uses {@link String#indexOf(int, int)} if possible.

              + * + *

              A {@code null} or empty ("") CharSequence will return {@code (INDEX_NOT_FOUND) -1}. + * A negative start position is treated as zero. + * A start position greater than the string length returns {@code -1}.

              + * + *
              +     * StringUtils.indexOf(null, *, *)          = -1
              +     * StringUtils.indexOf("", *, *)            = -1
              +     * StringUtils.indexOf("aabaabaa", 'b', 0)  = 2
              +     * StringUtils.indexOf("aabaabaa", 'b', 3)  = 5
              +     * StringUtils.indexOf("aabaabaa", 'b', 9)  = -1
              +     * StringUtils.indexOf("aabaabaa", 'b', -1) = 2
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchChar the character to find + * @param startPos the start position, negative treated as zero + * @return the first index of the search character (always ≥ startPos), + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from indexOf(String, int, int) to indexOf(CharSequence, int, int) + */ + public static int indexOf(final CharSequence seq, final int searchChar, final int startPos) { + if (isEmpty(seq)) { + return INDEX_NOT_FOUND; + } + return CharSequenceUtils.indexOf(seq, searchChar, startPos); + } + + /** + *

              Finds the first index within a CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(String, int)} if possible.

              + * + *

              A {@code null} CharSequence will return {@code -1}.

              + * + *
              +     * StringUtils.indexOf(null, *)          = -1
              +     * StringUtils.indexOf(*, null)          = -1
              +     * StringUtils.indexOf("", "")           = 0
              +     * StringUtils.indexOf("", *)            = -1 (except when * = "")
              +     * StringUtils.indexOf("aabaabaa", "a")  = 0
              +     * StringUtils.indexOf("aabaabaa", "b")  = 2
              +     * StringUtils.indexOf("aabaabaa", "ab") = 1
              +     * StringUtils.indexOf("aabaabaa", "")   = 0
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @return the first index of the search CharSequence, + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from indexOf(String, String) to indexOf(CharSequence, CharSequence) + */ + public static int indexOf(final CharSequence seq, final CharSequence searchSeq) { + if (seq == null || searchSeq == null) { + return INDEX_NOT_FOUND; + } + return CharSequenceUtils.indexOf(seq, searchSeq, 0); + } + + /** + *

              Finds the first index within a CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(String, int)} if possible.

              + * + *

              A {@code null} CharSequence will return {@code -1}. + * A negative start position is treated as zero. + * An empty ("") search CharSequence always matches. + * A start position greater than the string length only matches + * an empty search CharSequence.

              + * + *
              +     * StringUtils.indexOf(null, *, *)          = -1
              +     * StringUtils.indexOf(*, null, *)          = -1
              +     * StringUtils.indexOf("", "", 0)           = 0
              +     * StringUtils.indexOf("", *, 0)            = -1 (except when * = "")
              +     * StringUtils.indexOf("aabaabaa", "a", 0)  = 0
              +     * StringUtils.indexOf("aabaabaa", "b", 0)  = 2
              +     * StringUtils.indexOf("aabaabaa", "ab", 0) = 1
              +     * StringUtils.indexOf("aabaabaa", "b", 3)  = 5
              +     * StringUtils.indexOf("aabaabaa", "b", 9)  = -1
              +     * StringUtils.indexOf("aabaabaa", "b", -1) = 2
              +     * StringUtils.indexOf("aabaabaa", "", 2)   = 2
              +     * StringUtils.indexOf("abc", "", 9)        = 3
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @param startPos the start position, negative treated as zero + * @return the first index of the search CharSequence (always ≥ startPos), + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from indexOf(String, String, int) to indexOf(CharSequence, CharSequence, int) + */ + public static int indexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) { + if (seq == null || searchSeq == null) { + return INDEX_NOT_FOUND; + } + return CharSequenceUtils.indexOf(seq, searchSeq, startPos); + } + + /** + *

              Finds the n-th index within a CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(String)} if possible.

              + * + *

              A {@code null} CharSequence will return {@code -1}.

              + * + *
              +     * StringUtils.ordinalIndexOf(null, *, *)          = -1
              +     * StringUtils.ordinalIndexOf(*, null, *)          = -1
              +     * StringUtils.ordinalIndexOf("", "", *)           = 0
              +     * StringUtils.ordinalIndexOf("aabaabaa", "a", 1)  = 0
              +     * StringUtils.ordinalIndexOf("aabaabaa", "a", 2)  = 1
              +     * StringUtils.ordinalIndexOf("aabaabaa", "b", 1)  = 2
              +     * StringUtils.ordinalIndexOf("aabaabaa", "b", 2)  = 5
              +     * StringUtils.ordinalIndexOf("aabaabaa", "ab", 1) = 1
              +     * StringUtils.ordinalIndexOf("aabaabaa", "ab", 2) = 4
              +     * StringUtils.ordinalIndexOf("aabaabaa", "", 1)   = 0
              +     * StringUtils.ordinalIndexOf("aabaabaa", "", 2)   = 0
              +     * 
              + * + *

              Note that 'head(CharSequence str, int n)' may be implemented as:

              + * + *
              +     *   str.substring(0, lastOrdinalIndexOf(str, "\n", n))
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param ordinal the n-th {@code searchStr} to find + * @return the n-th index of the search CharSequence, + * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input + * @since 2.1 + * @since 3.0 Changed signature from ordinalIndexOf(String, String, int) to ordinalIndexOf(CharSequence, CharSequence, int) + */ + public static int ordinalIndexOf(final CharSequence str, final CharSequence searchStr, final int ordinal) { + return ordinalIndexOf(str, searchStr, ordinal, false); + } + + /** + *

              Finds the n-th index within a String, handling {@code null}. + * This method uses {@link String#indexOf(String)} if possible.

              + * + *

              A {@code null} CharSequence will return {@code -1}.

              + * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param ordinal the n-th {@code searchStr} to find + * @param lastIndex true if lastOrdinalIndexOf() otherwise false if ordinalIndexOf() + * @return the n-th index of the search CharSequence, + * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input + */ + // Shared code between ordinalIndexOf(String,String,int) and lastOrdinalIndexOf(String,String,int) + private static int ordinalIndexOf(final CharSequence str, final CharSequence searchStr, final int ordinal, final boolean lastIndex) { + if (str == null || searchStr == null || ordinal <= 0) { + return INDEX_NOT_FOUND; + } + if (searchStr.length() == 0) { + return lastIndex ? str.length() : 0; + } + int found = 0; + int index = lastIndex ? str.length() : INDEX_NOT_FOUND; + do { + if (lastIndex) { + index = CharSequenceUtils.lastIndexOf(str, searchStr, index - searchStr.length()); + } else { + index = CharSequenceUtils.indexOf(str, searchStr, index + searchStr.length()); + } + if (index < 0) { + return index; + } + found++; + } while (found < ordinal); + return index; + } + + /** + *

              Case in-sensitive find of the first index within a CharSequence.

              + * + *

              A {@code null} CharSequence will return {@code -1}. + * A negative start position is treated as zero. + * An empty ("") search CharSequence always matches. + * A start position greater than the string length only matches + * an empty search CharSequence.

              + * + *
              +     * StringUtils.indexOfIgnoreCase(null, *)          = -1
              +     * StringUtils.indexOfIgnoreCase(*, null)          = -1
              +     * StringUtils.indexOfIgnoreCase("", "")           = 0
              +     * StringUtils.indexOfIgnoreCase("aabaabaa", "a")  = 0
              +     * StringUtils.indexOfIgnoreCase("aabaabaa", "b")  = 2
              +     * StringUtils.indexOfIgnoreCase("aabaabaa", "ab") = 1
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @return the first index of the search CharSequence, + * -1 if no match or {@code null} string input + * @since 2.5 + * @since 3.0 Changed signature from indexOfIgnoreCase(String, String) to indexOfIgnoreCase(CharSequence, CharSequence) + */ + public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) { + return indexOfIgnoreCase(str, searchStr, 0); + } + + /** + *

              Case in-sensitive find of the first index within a CharSequence + * from the specified position.

              + * + *

              A {@code null} CharSequence will return {@code -1}. + * A negative start position is treated as zero. + * An empty ("") search CharSequence always matches. + * A start position greater than the string length only matches + * an empty search CharSequence.

              + * + *
              +     * StringUtils.indexOfIgnoreCase(null, *, *)          = -1
              +     * StringUtils.indexOfIgnoreCase(*, null, *)          = -1
              +     * StringUtils.indexOfIgnoreCase("", "", 0)           = 0
              +     * StringUtils.indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
              +     * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
              +     * StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
              +     * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
              +     * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
              +     * StringUtils.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
              +     * StringUtils.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
              +     * StringUtils.indexOfIgnoreCase("abc", "", 9)        = 3
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param startPos the start position, negative treated as zero + * @return the first index of the search CharSequence (always ≥ startPos), + * -1 if no match or {@code null} string input + * @since 2.5 + * @since 3.0 Changed signature from indexOfIgnoreCase(String, String, int) to indexOfIgnoreCase(CharSequence, CharSequence, int) + */ + public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) { + if (str == null || searchStr == null) { + return INDEX_NOT_FOUND; + } + if (startPos < 0) { + startPos = 0; + } + final int endLimit = str.length() - searchStr.length() + 1; + if (startPos > endLimit) { + return INDEX_NOT_FOUND; + } + if (searchStr.length() == 0) { + return startPos; + } + for (int i = startPos; i < endLimit; i++) { + if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStr.length())) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + // LastIndexOf + //----------------------------------------------------------------------- + /** + *

              Finds the last index within a CharSequence, handling {@code null}. + * This method uses {@link String#lastIndexOf(int)} if possible.

              + * + *

              A {@code null} or empty ("") CharSequence will return {@code -1}.

              + * + *
              +     * StringUtils.lastIndexOf(null, *)         = -1
              +     * StringUtils.lastIndexOf("", *)           = -1
              +     * StringUtils.lastIndexOf("aabaabaa", 'a') = 7
              +     * StringUtils.lastIndexOf("aabaabaa", 'b') = 5
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchChar the character to find + * @return the last index of the search character, + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from lastIndexOf(String, int) to lastIndexOf(CharSequence, int) + */ + public static int lastIndexOf(final CharSequence seq, final int searchChar) { + if (isEmpty(seq)) { + return INDEX_NOT_FOUND; + } + return CharSequenceUtils.lastIndexOf(seq, searchChar, seq.length()); + } + + /** + *

              Finds the last index within a CharSequence from a start position, + * handling {@code null}. + * This method uses {@link String#lastIndexOf(int, int)} if possible.

              + * + *

              A {@code null} or empty ("") CharSequence will return {@code -1}. + * A negative start position returns {@code -1}. + * A start position greater than the string length searches the whole string. + * The search starts at the startPos and works backwards; matches starting after the start + * position are ignored. + *

              + * + *
              +     * StringUtils.lastIndexOf(null, *, *)          = -1
              +     * StringUtils.lastIndexOf("", *,  *)           = -1
              +     * StringUtils.lastIndexOf("aabaabaa", 'b', 8)  = 5
              +     * StringUtils.lastIndexOf("aabaabaa", 'b', 4)  = 2
              +     * StringUtils.lastIndexOf("aabaabaa", 'b', 0)  = -1
              +     * StringUtils.lastIndexOf("aabaabaa", 'b', 9)  = 5
              +     * StringUtils.lastIndexOf("aabaabaa", 'b', -1) = -1
              +     * StringUtils.lastIndexOf("aabaabaa", 'a', 0)  = 0
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchChar the character to find + * @param startPos the start position + * @return the last index of the search character (always ≤ startPos), + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from lastIndexOf(String, int, int) to lastIndexOf(CharSequence, int, int) + */ + public static int lastIndexOf(final CharSequence seq, final int searchChar, final int startPos) { + if (isEmpty(seq)) { + return INDEX_NOT_FOUND; + } + return CharSequenceUtils.lastIndexOf(seq, searchChar, startPos); + } + + /** + *

              Finds the last index within a CharSequence, handling {@code null}. + * This method uses {@link String#lastIndexOf(String)} if possible.

              + * + *

              A {@code null} CharSequence will return {@code -1}.

              + * + *
              +     * StringUtils.lastIndexOf(null, *)          = -1
              +     * StringUtils.lastIndexOf(*, null)          = -1
              +     * StringUtils.lastIndexOf("", "")           = 0
              +     * StringUtils.lastIndexOf("aabaabaa", "a")  = 7
              +     * StringUtils.lastIndexOf("aabaabaa", "b")  = 5
              +     * StringUtils.lastIndexOf("aabaabaa", "ab") = 4
              +     * StringUtils.lastIndexOf("aabaabaa", "")   = 8
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @return the last index of the search String, + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from lastIndexOf(String, String) to lastIndexOf(CharSequence, CharSequence) + */ + public static int lastIndexOf(final CharSequence seq, final CharSequence searchSeq) { + if (seq == null || searchSeq == null) { + return INDEX_NOT_FOUND; + } + return CharSequenceUtils.lastIndexOf(seq, searchSeq, seq.length()); + } + + /** + *

              Finds the n-th last index within a String, handling {@code null}. + * This method uses {@link String#lastIndexOf(String)}.

              + * + *

              A {@code null} String will return {@code -1}.

              + * + *
              +     * StringUtils.lastOrdinalIndexOf(null, *, *)          = -1
              +     * StringUtils.lastOrdinalIndexOf(*, null, *)          = -1
              +     * StringUtils.lastOrdinalIndexOf("", "", *)           = 0
              +     * StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 1)  = 7
              +     * StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 2)  = 6
              +     * StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 1)  = 5
              +     * StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 2)  = 2
              +     * StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 1) = 4
              +     * StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 2) = 1
              +     * StringUtils.lastOrdinalIndexOf("aabaabaa", "", 1)   = 8
              +     * StringUtils.lastOrdinalIndexOf("aabaabaa", "", 2)   = 8
              +     * 
              + * + *

              Note that 'tail(CharSequence str, int n)' may be implemented as:

              + * + *
              +     *   str.substring(lastOrdinalIndexOf(str, "\n", n) + 1)
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param ordinal the n-th last {@code searchStr} to find + * @return the n-th last index of the search CharSequence, + * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input + * @since 2.5 + * @since 3.0 Changed signature from lastOrdinalIndexOf(String, String, int) to lastOrdinalIndexOf(CharSequence, CharSequence, int) + */ + public static int lastOrdinalIndexOf(final CharSequence str, final CharSequence searchStr, final int ordinal) { + return ordinalIndexOf(str, searchStr, ordinal, true); + } + + /** + *

              Finds the last index within a CharSequence, handling {@code null}. + * This method uses {@link String#lastIndexOf(String, int)} if possible.

              + * + *

              A {@code null} CharSequence will return {@code -1}. + * A negative start position returns {@code -1}. + * An empty ("") search CharSequence always matches unless the start position is negative. + * A start position greater than the string length searches the whole string. + * The search starts at the startPos and works backwards; matches starting after the start + * position are ignored. + *

              + * + *
              +     * StringUtils.lastIndexOf(null, *, *)          = -1
              +     * StringUtils.lastIndexOf(*, null, *)          = -1
              +     * StringUtils.lastIndexOf("aabaabaa", "a", 8)  = 7
              +     * StringUtils.lastIndexOf("aabaabaa", "b", 8)  = 5
              +     * StringUtils.lastIndexOf("aabaabaa", "ab", 8) = 4
              +     * StringUtils.lastIndexOf("aabaabaa", "b", 9)  = 5
              +     * StringUtils.lastIndexOf("aabaabaa", "b", -1) = -1
              +     * StringUtils.lastIndexOf("aabaabaa", "a", 0)  = 0
              +     * StringUtils.lastIndexOf("aabaabaa", "b", 0)  = -1
              +     * StringUtils.lastIndexOf("aabaabaa", "b", 1)  = -1
              +     * StringUtils.lastIndexOf("aabaabaa", "b", 2)  = 2
              +     * StringUtils.lastIndexOf("aabaabaa", "ba", 2)  = -1
              +     * StringUtils.lastIndexOf("aabaabaa", "ba", 2)  = 2
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @param startPos the start position, negative treated as zero + * @return the last index of the search CharSequence (always ≤ startPos), + * -1 if no match or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from lastIndexOf(String, String, int) to lastIndexOf(CharSequence, CharSequence, int) + */ + public static int lastIndexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) { + if (seq == null || searchSeq == null) { + return INDEX_NOT_FOUND; + } + return CharSequenceUtils.lastIndexOf(seq, searchSeq, startPos); + } + + /** + *

              Case in-sensitive find of the last index within a CharSequence.

              + * + *

              A {@code null} CharSequence will return {@code -1}. + * A negative start position returns {@code -1}. + * An empty ("") search CharSequence always matches unless the start position is negative. + * A start position greater than the string length searches the whole string.

              + * + *
              +     * StringUtils.lastIndexOfIgnoreCase(null, *)          = -1
              +     * StringUtils.lastIndexOfIgnoreCase(*, null)          = -1
              +     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A")  = 7
              +     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B")  = 5
              +     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB") = 4
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @return the first index of the search CharSequence, + * -1 if no match or {@code null} string input + * @since 2.5 + * @since 3.0 Changed signature from lastIndexOfIgnoreCase(String, String) to lastIndexOfIgnoreCase(CharSequence, CharSequence) + */ + public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) { + if (str == null || searchStr == null) { + return INDEX_NOT_FOUND; + } + return lastIndexOfIgnoreCase(str, searchStr, str.length()); + } + + /** + *

              Case in-sensitive find of the last index within a CharSequence + * from the specified position.

              + * + *

              A {@code null} CharSequence will return {@code -1}. + * A negative start position returns {@code -1}. + * An empty ("") search CharSequence always matches unless the start position is negative. + * A start position greater than the string length searches the whole string. + * The search starts at the startPos and works backwards; matches starting after the start + * position are ignored. + *

              + * + *
              +     * StringUtils.lastIndexOfIgnoreCase(null, *, *)          = -1
              +     * StringUtils.lastIndexOfIgnoreCase(*, null, *)          = -1
              +     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 8)  = 7
              +     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 8)  = 5
              +     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB", 8) = 4
              +     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 9)  = 5
              +     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", -1) = -1
              +     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 0)  = 0
              +     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 0)  = -1
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param startPos the start position + * @return the last index of the search CharSequence (always ≤ startPos), + * -1 if no match or {@code null} input + * @since 2.5 + * @since 3.0 Changed signature from lastIndexOfIgnoreCase(String, String, int) to lastIndexOfIgnoreCase(CharSequence, CharSequence, int) + */ + public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) { + if (str == null || searchStr == null) { + return INDEX_NOT_FOUND; + } + if (startPos > str.length() - searchStr.length()) { + startPos = str.length() - searchStr.length(); + } + if (startPos < 0) { + return INDEX_NOT_FOUND; + } + if (searchStr.length() == 0) { + return startPos; + } + + for (int i = startPos; i >= 0; i--) { + if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStr.length())) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + // Contains + //----------------------------------------------------------------------- + /** + *

              Checks if CharSequence contains a search character, handling {@code null}. + * This method uses {@link String#indexOf(int)} if possible.

              + * + *

              A {@code null} or empty ("") CharSequence will return {@code false}.

              + * + *
              +     * StringUtils.contains(null, *)    = false
              +     * StringUtils.contains("", *)      = false
              +     * StringUtils.contains("abc", 'a') = true
              +     * StringUtils.contains("abc", 'z') = false
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchChar the character to find + * @return true if the CharSequence contains the search character, + * false if not or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from contains(String, int) to contains(CharSequence, int) + */ + public static boolean contains(final CharSequence seq, final int searchChar) { + if (isEmpty(seq)) { + return false; + } + return CharSequenceUtils.indexOf(seq, searchChar, 0) >= 0; + } + + /** + *

              Checks if CharSequence contains a search CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(String)} if possible.

              + * + *

              A {@code null} CharSequence will return {@code false}.

              + * + *
              +     * StringUtils.contains(null, *)     = false
              +     * StringUtils.contains(*, null)     = false
              +     * StringUtils.contains("", "")      = true
              +     * StringUtils.contains("abc", "")   = true
              +     * StringUtils.contains("abc", "a")  = true
              +     * StringUtils.contains("abc", "z")  = false
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @return true if the CharSequence contains the search CharSequence, + * false if not or {@code null} string input + * @since 2.0 + * @since 3.0 Changed signature from contains(String, String) to contains(CharSequence, CharSequence) + */ + public static boolean contains(final CharSequence seq, final CharSequence searchSeq) { + if (seq == null || searchSeq == null) { + return false; + } + return CharSequenceUtils.indexOf(seq, searchSeq, 0) >= 0; + } + + /** + *

              Checks if CharSequence contains a search CharSequence irrespective of case, + * handling {@code null}. Case-insensitivity is defined as by + * {@link String#equalsIgnoreCase(String)}. + * + *

              A {@code null} CharSequence will return {@code false}.

              + * + *
              +     * StringUtils.contains(null, *) = false
              +     * StringUtils.contains(*, null) = false
              +     * StringUtils.contains("", "") = true
              +     * StringUtils.contains("abc", "") = true
              +     * StringUtils.contains("abc", "a") = true
              +     * StringUtils.contains("abc", "z") = false
              +     * StringUtils.contains("abc", "A") = true
              +     * StringUtils.contains("abc", "Z") = false
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @return true if the CharSequence contains the search CharSequence irrespective of + * case or false if not or {@code null} string input + * @since 3.0 Changed signature from containsIgnoreCase(String, String) to containsIgnoreCase(CharSequence, CharSequence) + */ + public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr) { + if (str == null || searchStr == null) { + return false; + } + final int len = searchStr.length(); + final int max = str.length() - len; + for (int i = 0; i <= max; i++) { + if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, len)) { + return true; + } + } + return false; + } + + /** + * Check whether the given CharSequence contains any whitespace characters. + * @param seq the CharSequence to check (may be {@code null}) + * @return {@code true} if the CharSequence is not empty and + * contains at least 1 whitespace character + * @see java.lang.Character#isWhitespace + * @since 3.0 + */ + // From org.springframework.util.StringUtils, under Apache License 2.0 + public static boolean containsWhitespace(final CharSequence seq) { + if (isEmpty(seq)) { + return false; + } + final int strLen = seq.length(); + for (int i = 0; i < strLen; i++) { + if (Character.isWhitespace(seq.charAt(i))) { + return true; + } + } + return false; + } + + // IndexOfAny chars + //----------------------------------------------------------------------- + /** + *

              Search a CharSequence to find the first index of any + * character in the given set of characters.

              + * + *

              A {@code null} String will return {@code -1}. + * A {@code null} or zero length search array will return {@code -1}.

              + * + *
              +     * StringUtils.indexOfAny(null, *)                = -1
              +     * StringUtils.indexOfAny("", *)                  = -1
              +     * StringUtils.indexOfAny(*, null)                = -1
              +     * StringUtils.indexOfAny(*, [])                  = -1
              +     * StringUtils.indexOfAny("zzabyycdxx",['z','a']) = 0
              +     * StringUtils.indexOfAny("zzabyycdxx",['b','y']) = 3
              +     * StringUtils.indexOfAny("aba", ['z'])           = -1
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @param searchChars the chars to search for, may be null + * @return the index of any of the chars, -1 if no match or null input + * @since 2.0 + * @since 3.0 Changed signature from indexOfAny(String, char[]) to indexOfAny(CharSequence, char...) + */ + public static int indexOfAny(final CharSequence cs, final char... searchChars) { + if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) { + return INDEX_NOT_FOUND; + } + final int csLen = cs.length(); + final int csLast = csLen - 1; + final int searchLen = searchChars.length; + final int searchLast = searchLen - 1; + for (int i = 0; i < csLen; i++) { + final char ch = cs.charAt(i); + for (int j = 0; j < searchLen; j++) { + if (searchChars[j] == ch) { + if (i < csLast && j < searchLast && Character.isHighSurrogate(ch)) { + // ch is a supplementary character + if (searchChars[j + 1] == cs.charAt(i + 1)) { + return i; + } + } else { + return i; + } + } + } + } + return INDEX_NOT_FOUND; + } + + /** + *

              Search a CharSequence to find the first index of any + * character in the given set of characters.

              + * + *

              A {@code null} String will return {@code -1}. + * A {@code null} search string will return {@code -1}.

              + * + *
              +     * StringUtils.indexOfAny(null, *)            = -1
              +     * StringUtils.indexOfAny("", *)              = -1
              +     * StringUtils.indexOfAny(*, null)            = -1
              +     * StringUtils.indexOfAny(*, "")              = -1
              +     * StringUtils.indexOfAny("zzabyycdxx", "za") = 0
              +     * StringUtils.indexOfAny("zzabyycdxx", "by") = 3
              +     * StringUtils.indexOfAny("aba","z")          = -1
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @param searchChars the chars to search for, may be null + * @return the index of any of the chars, -1 if no match or null input + * @since 2.0 + * @since 3.0 Changed signature from indexOfAny(String, String) to indexOfAny(CharSequence, String) + */ + public static int indexOfAny(final CharSequence cs, final String searchChars) { + if (isEmpty(cs) || isEmpty(searchChars)) { + return INDEX_NOT_FOUND; + } + return indexOfAny(cs, searchChars.toCharArray()); + } + + // ContainsAny + //----------------------------------------------------------------------- + /** + *

              Checks if the CharSequence contains any character in the given + * set of characters.

              + * + *

              A {@code null} CharSequence will return {@code false}. + * A {@code null} or zero length search array will return {@code false}.

              + * + *
              +     * StringUtils.containsAny(null, *)                = false
              +     * StringUtils.containsAny("", *)                  = false
              +     * StringUtils.containsAny(*, null)                = false
              +     * StringUtils.containsAny(*, [])                  = false
              +     * StringUtils.containsAny("zzabyycdxx",['z','a']) = true
              +     * StringUtils.containsAny("zzabyycdxx",['b','y']) = true
              +     * StringUtils.containsAny("aba", ['z'])           = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @param searchChars the chars to search for, may be null + * @return the {@code true} if any of the chars are found, + * {@code false} if no match or null input + * @since 2.4 + * @since 3.0 Changed signature from containsAny(String, char[]) to containsAny(CharSequence, char...) + */ + public static boolean containsAny(final CharSequence cs, final char... searchChars) { + if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) { + return false; + } + final int csLength = cs.length(); + final int searchLength = searchChars.length; + final int csLast = csLength - 1; + final int searchLast = searchLength - 1; + for (int i = 0; i < csLength; i++) { + final char ch = cs.charAt(i); + for (int j = 0; j < searchLength; j++) { + if (searchChars[j] == ch) { + if (Character.isHighSurrogate(ch)) { + if (j == searchLast) { + // missing low surrogate, fine, like String.indexOf(String) + return true; + } + if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) { + return true; + } + } else { + // ch is in the Basic Multilingual Plane + return true; + } + } + } + } + return false; + } + + /** + *

              + * Checks if the CharSequence contains any character in the given set of characters. + *

              + * + *

              + * A {@code null} CharSequence will return {@code false}. A {@code null} search CharSequence will return + * {@code false}. + *

              + * + *
              +     * StringUtils.containsAny(null, *)            = false
              +     * StringUtils.containsAny("", *)              = false
              +     * StringUtils.containsAny(*, null)            = false
              +     * StringUtils.containsAny(*, "")              = false
              +     * StringUtils.containsAny("zzabyycdxx", "za") = true
              +     * StringUtils.containsAny("zzabyycdxx", "by") = true
              +     * StringUtils.containsAny("aba","z")          = false
              +     * 
              + * + * @param cs + * the CharSequence to check, may be null + * @param searchChars + * the chars to search for, may be null + * @return the {@code true} if any of the chars are found, {@code false} if no match or null input + * @since 2.4 + * @since 3.0 Changed signature from containsAny(String, String) to containsAny(CharSequence, CharSequence) + */ + public static boolean containsAny(final CharSequence cs, final CharSequence searchChars) { + if (searchChars == null) { + return false; + } + return containsAny(cs, CharSequenceUtils.toCharArray(searchChars)); + } + + /** + *

              Checks if the CharSequence contains any of the CharSequences in the given array.

              + * + *

              + * A {@code null} CharSequence will return {@code false}. A {@code null} or zero + * length search array will return {@code false}. + *

              + * + *
              +     * StringUtils.containsAny(null, *)            = false
              +     * StringUtils.containsAny("", *)              = false
              +     * StringUtils.containsAny(*, null)            = false
              +     * StringUtils.containsAny(*, [])              = false
              +     * StringUtils.containsAny("abcd", "ab", "cd") = false
              +     * StringUtils.containsAny("abc", "d", "abc")  = true
              +     * 
              + * + * + * @param cs The CharSequence to check, may be null + * @param searchCharSequences The array of CharSequences to search for, may be null + * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise + * @since 3.4 + */ + public static boolean containsAny(CharSequence cs, CharSequence... searchCharSequences) { + if (isEmpty(cs) || ArrayUtils.isEmpty(searchCharSequences)) { + return false; + } + for (CharSequence searchCharSequence : searchCharSequences) { + if (contains(cs, searchCharSequence)) { + return true; + } + } + return false; + } + + // IndexOfAnyBut chars + //----------------------------------------------------------------------- + /** + *

              Searches a CharSequence to find the first index of any + * character not in the given set of characters.

              + * + *

              A {@code null} CharSequence will return {@code -1}. + * A {@code null} or zero length search array will return {@code -1}.

              + * + *
              +     * StringUtils.indexOfAnyBut(null, *)                              = -1
              +     * StringUtils.indexOfAnyBut("", *)                                = -1
              +     * StringUtils.indexOfAnyBut(*, null)                              = -1
              +     * StringUtils.indexOfAnyBut(*, [])                                = -1
              +     * StringUtils.indexOfAnyBut("zzabyycdxx", new char[] {'z', 'a'} ) = 3
              +     * StringUtils.indexOfAnyBut("aba", new char[] {'z'} )             = 0
              +     * StringUtils.indexOfAnyBut("aba", new char[] {'a', 'b'} )        = -1
              +
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @param searchChars the chars to search for, may be null + * @return the index of any of the chars, -1 if no match or null input + * @since 2.0 + * @since 3.0 Changed signature from indexOfAnyBut(String, char[]) to indexOfAnyBut(CharSequence, char...) + */ + public static int indexOfAnyBut(final CharSequence cs, final char... searchChars) { + if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) { + return INDEX_NOT_FOUND; + } + final int csLen = cs.length(); + final int csLast = csLen - 1; + final int searchLen = searchChars.length; + final int searchLast = searchLen - 1; + outer: + for (int i = 0; i < csLen; i++) { + final char ch = cs.charAt(i); + for (int j = 0; j < searchLen; j++) { + if (searchChars[j] == ch) { + if (i < csLast && j < searchLast && Character.isHighSurrogate(ch)) { + if (searchChars[j + 1] == cs.charAt(i + 1)) { + continue outer; + } + } else { + continue outer; + } + } + } + return i; + } + return INDEX_NOT_FOUND; + } + + /** + *

              Search a CharSequence to find the first index of any + * character not in the given set of characters.

              + * + *

              A {@code null} CharSequence will return {@code -1}. + * A {@code null} or empty search string will return {@code -1}.

              + * + *
              +     * StringUtils.indexOfAnyBut(null, *)            = -1
              +     * StringUtils.indexOfAnyBut("", *)              = -1
              +     * StringUtils.indexOfAnyBut(*, null)            = -1
              +     * StringUtils.indexOfAnyBut(*, "")              = -1
              +     * StringUtils.indexOfAnyBut("zzabyycdxx", "za") = 3
              +     * StringUtils.indexOfAnyBut("zzabyycdxx", "")   = -1
              +     * StringUtils.indexOfAnyBut("aba","ab")         = -1
              +     * 
              + * + * @param seq the CharSequence to check, may be null + * @param searchChars the chars to search for, may be null + * @return the index of any of the chars, -1 if no match or null input + * @since 2.0 + * @since 3.0 Changed signature from indexOfAnyBut(String, String) to indexOfAnyBut(CharSequence, CharSequence) + */ + public static int indexOfAnyBut(final CharSequence seq, final CharSequence searchChars) { + if (isEmpty(seq) || isEmpty(searchChars)) { + return INDEX_NOT_FOUND; + } + final int strLen = seq.length(); + for (int i = 0; i < strLen; i++) { + final char ch = seq.charAt(i); + final boolean chFound = CharSequenceUtils.indexOf(searchChars, ch, 0) >= 0; + if (i + 1 < strLen && Character.isHighSurrogate(ch)) { + final char ch2 = seq.charAt(i + 1); + if (chFound && CharSequenceUtils.indexOf(searchChars, ch2, 0) < 0) { + return i; + } + } else { + if (!chFound) { + return i; + } + } + } + return INDEX_NOT_FOUND; + } + + // ContainsOnly + //----------------------------------------------------------------------- + /** + *

              Checks if the CharSequence contains only certain characters.

              + * + *

              A {@code null} CharSequence will return {@code false}. + * A {@code null} valid character array will return {@code false}. + * An empty CharSequence (length()=0) always returns {@code true}.

              + * + *
              +     * StringUtils.containsOnly(null, *)       = false
              +     * StringUtils.containsOnly(*, null)       = false
              +     * StringUtils.containsOnly("", *)         = true
              +     * StringUtils.containsOnly("ab", '')      = false
              +     * StringUtils.containsOnly("abab", 'abc') = true
              +     * StringUtils.containsOnly("ab1", 'abc')  = false
              +     * StringUtils.containsOnly("abz", 'abc')  = false
              +     * 
              + * + * @param cs the String to check, may be null + * @param valid an array of valid chars, may be null + * @return true if it only contains valid chars and is non-null + * @since 3.0 Changed signature from containsOnly(String, char[]) to containsOnly(CharSequence, char...) + */ + public static boolean containsOnly(final CharSequence cs, final char... valid) { + // All these pre-checks are to maintain API with an older version + if (valid == null || cs == null) { + return false; + } + if (cs.length() == 0) { + return true; + } + if (valid.length == 0) { + return false; + } + return indexOfAnyBut(cs, valid) == INDEX_NOT_FOUND; + } + + /** + *

              Checks if the CharSequence contains only certain characters.

              + * + *

              A {@code null} CharSequence will return {@code false}. + * A {@code null} valid character String will return {@code false}. + * An empty String (length()=0) always returns {@code true}.

              + * + *
              +     * StringUtils.containsOnly(null, *)       = false
              +     * StringUtils.containsOnly(*, null)       = false
              +     * StringUtils.containsOnly("", *)         = true
              +     * StringUtils.containsOnly("ab", "")      = false
              +     * StringUtils.containsOnly("abab", "abc") = true
              +     * StringUtils.containsOnly("ab1", "abc")  = false
              +     * StringUtils.containsOnly("abz", "abc")  = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @param validChars a String of valid chars, may be null + * @return true if it only contains valid chars and is non-null + * @since 2.0 + * @since 3.0 Changed signature from containsOnly(String, String) to containsOnly(CharSequence, String) + */ + public static boolean containsOnly(final CharSequence cs, final String validChars) { + if (cs == null || validChars == null) { + return false; + } + return containsOnly(cs, validChars.toCharArray()); + } + + // ContainsNone + //----------------------------------------------------------------------- + /** + *

              Checks that the CharSequence does not contain certain characters.

              + * + *

              A {@code null} CharSequence will return {@code true}. + * A {@code null} invalid character array will return {@code true}. + * An empty CharSequence (length()=0) always returns true.

              + * + *
              +     * StringUtils.containsNone(null, *)       = true
              +     * StringUtils.containsNone(*, null)       = true
              +     * StringUtils.containsNone("", *)         = true
              +     * StringUtils.containsNone("ab", '')      = true
              +     * StringUtils.containsNone("abab", 'xyz') = true
              +     * StringUtils.containsNone("ab1", 'xyz')  = true
              +     * StringUtils.containsNone("abz", 'xyz')  = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @param searchChars an array of invalid chars, may be null + * @return true if it contains none of the invalid chars, or is null + * @since 2.0 + * @since 3.0 Changed signature from containsNone(String, char[]) to containsNone(CharSequence, char...) + */ + public static boolean containsNone(final CharSequence cs, final char... searchChars) { + if (cs == null || searchChars == null) { + return true; + } + final int csLen = cs.length(); + final int csLast = csLen - 1; + final int searchLen = searchChars.length; + final int searchLast = searchLen - 1; + for (int i = 0; i < csLen; i++) { + final char ch = cs.charAt(i); + for (int j = 0; j < searchLen; j++) { + if (searchChars[j] == ch) { + if (Character.isHighSurrogate(ch)) { + if (j == searchLast) { + // missing low surrogate, fine, like String.indexOf(String) + return false; + } + if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) { + return false; + } + } else { + // ch is in the Basic Multilingual Plane + return false; + } + } + } + } + return true; + } + + /** + *

              Checks that the CharSequence does not contain certain characters.

              + * + *

              A {@code null} CharSequence will return {@code true}. + * A {@code null} invalid character array will return {@code true}. + * An empty String ("") always returns true.

              + * + *
              +     * StringUtils.containsNone(null, *)       = true
              +     * StringUtils.containsNone(*, null)       = true
              +     * StringUtils.containsNone("", *)         = true
              +     * StringUtils.containsNone("ab", "")      = true
              +     * StringUtils.containsNone("abab", "xyz") = true
              +     * StringUtils.containsNone("ab1", "xyz")  = true
              +     * StringUtils.containsNone("abz", "xyz")  = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @param invalidChars a String of invalid chars, may be null + * @return true if it contains none of the invalid chars, or is null + * @since 2.0 + * @since 3.0 Changed signature from containsNone(String, String) to containsNone(CharSequence, String) + */ + public static boolean containsNone(final CharSequence cs, final String invalidChars) { + if (cs == null || invalidChars == null) { + return true; + } + return containsNone(cs, invalidChars.toCharArray()); + } + + // IndexOfAny strings + //----------------------------------------------------------------------- + /** + *

              Find the first index of any of a set of potential substrings.

              + * + *

              A {@code null} CharSequence will return {@code -1}. + * A {@code null} or zero length search array will return {@code -1}. + * A {@code null} search array entry will be ignored, but a search + * array containing "" will return {@code 0} if {@code str} is not + * null. This method uses {@link String#indexOf(String)} if possible.

              + * + *
              +     * StringUtils.indexOfAny(null, *)                     = -1
              +     * StringUtils.indexOfAny(*, null)                     = -1
              +     * StringUtils.indexOfAny(*, [])                       = -1
              +     * StringUtils.indexOfAny("zzabyycdxx", ["ab","cd"])   = 2
              +     * StringUtils.indexOfAny("zzabyycdxx", ["cd","ab"])   = 2
              +     * StringUtils.indexOfAny("zzabyycdxx", ["mn","op"])   = -1
              +     * StringUtils.indexOfAny("zzabyycdxx", ["zab","aby"]) = 1
              +     * StringUtils.indexOfAny("zzabyycdxx", [""])          = 0
              +     * StringUtils.indexOfAny("", [""])                    = 0
              +     * StringUtils.indexOfAny("", ["a"])                   = -1
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param searchStrs the CharSequences to search for, may be null + * @return the first index of any of the searchStrs in str, -1 if no match + * @since 3.0 Changed signature from indexOfAny(String, String[]) to indexOfAny(CharSequence, CharSequence...) + */ + public static int indexOfAny(final CharSequence str, final CharSequence... searchStrs) { + if (str == null || searchStrs == null) { + return INDEX_NOT_FOUND; + } + final int sz = searchStrs.length; + + // String's can't have a MAX_VALUEth index. + int ret = Integer.MAX_VALUE; + + int tmp = 0; + for (int i = 0; i < sz; i++) { + final CharSequence search = searchStrs[i]; + if (search == null) { + continue; + } + tmp = CharSequenceUtils.indexOf(str, search, 0); + if (tmp == INDEX_NOT_FOUND) { + continue; + } + + if (tmp < ret) { + ret = tmp; + } + } + + return ret == Integer.MAX_VALUE ? INDEX_NOT_FOUND : ret; + } + + /** + *

              Find the latest index of any of a set of potential substrings.

              + * + *

              A {@code null} CharSequence will return {@code -1}. + * A {@code null} search array will return {@code -1}. + * A {@code null} or zero length search array entry will be ignored, + * but a search array containing "" will return the length of {@code str} + * if {@code str} is not null. This method uses {@link String#indexOf(String)} if possible

              + * + *
              +     * StringUtils.lastIndexOfAny(null, *)                   = -1
              +     * StringUtils.lastIndexOfAny(*, null)                   = -1
              +     * StringUtils.lastIndexOfAny(*, [])                     = -1
              +     * StringUtils.lastIndexOfAny(*, [null])                 = -1
              +     * StringUtils.lastIndexOfAny("zzabyycdxx", ["ab","cd"]) = 6
              +     * StringUtils.lastIndexOfAny("zzabyycdxx", ["cd","ab"]) = 6
              +     * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn","op"]) = -1
              +     * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn","op"]) = -1
              +     * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn",""])   = 10
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param searchStrs the CharSequences to search for, may be null + * @return the last index of any of the CharSequences, -1 if no match + * @since 3.0 Changed signature from lastIndexOfAny(String, String[]) to lastIndexOfAny(CharSequence, CharSequence) + */ + public static int lastIndexOfAny(final CharSequence str, final CharSequence... searchStrs) { + if (str == null || searchStrs == null) { + return INDEX_NOT_FOUND; + } + final int sz = searchStrs.length; + int ret = INDEX_NOT_FOUND; + int tmp = 0; + for (int i = 0; i < sz; i++) { + final CharSequence search = searchStrs[i]; + if (search == null) { + continue; + } + tmp = CharSequenceUtils.lastIndexOf(str, search, str.length()); + if (tmp > ret) { + ret = tmp; + } + } + return ret; + } + + // Substring + //----------------------------------------------------------------------- + /** + *

              Gets a substring from the specified String avoiding exceptions.

              + * + *

              A negative start position can be used to start {@code n} + * characters from the end of the String.

              + * + *

              A {@code null} String will return {@code null}. + * An empty ("") String will return "".

              + * + *
              +     * StringUtils.substring(null, *)   = null
              +     * StringUtils.substring("", *)     = ""
              +     * StringUtils.substring("abc", 0)  = "abc"
              +     * StringUtils.substring("abc", 2)  = "c"
              +     * StringUtils.substring("abc", 4)  = ""
              +     * StringUtils.substring("abc", -2) = "bc"
              +     * StringUtils.substring("abc", -4) = "abc"
              +     * 
              + * + * @param str the String to get the substring from, may be null + * @param start the position to start from, negative means + * count back from the end of the String by this many characters + * @return substring from start position, {@code null} if null String input + */ + public static String substring(final String str, int start) { + if (str == null) { + return null; + } + + // handle negatives, which means last n characters + if (start < 0) { + start = str.length() + start; // remember start is negative + } + + if (start < 0) { + start = 0; + } + if (start > str.length()) { + return EMPTY; + } + + return str.substring(start); + } + + /** + *

              Gets a substring from the specified String avoiding exceptions.

              + * + *

              A negative start position can be used to start/end {@code n} + * characters from the end of the String.

              + * + *

              The returned substring starts with the character in the {@code start} + * position and ends before the {@code end} position. All position counting is + * zero-based -- i.e., to start at the beginning of the string use + * {@code start = 0}. Negative start and end positions can be used to + * specify offsets relative to the end of the String.

              + * + *

              If {@code start} is not strictly to the left of {@code end}, "" + * is returned.

              + * + *
              +     * StringUtils.substring(null, *, *)    = null
              +     * StringUtils.substring("", * ,  *)    = "";
              +     * StringUtils.substring("abc", 0, 2)   = "ab"
              +     * StringUtils.substring("abc", 2, 0)   = ""
              +     * StringUtils.substring("abc", 2, 4)   = "c"
              +     * StringUtils.substring("abc", 4, 6)   = ""
              +     * StringUtils.substring("abc", 2, 2)   = ""
              +     * StringUtils.substring("abc", -2, -1) = "b"
              +     * StringUtils.substring("abc", -4, 2)  = "ab"
              +     * 
              + * + * @param str the String to get the substring from, may be null + * @param start the position to start from, negative means + * count back from the end of the String by this many characters + * @param end the position to end at (exclusive), negative means + * count back from the end of the String by this many characters + * @return substring from start position to end position, + * {@code null} if null String input + */ + public static String substring(final String str, int start, int end) { + if (str == null) { + return null; + } + + // handle negatives + if (end < 0) { + end = str.length() + end; // remember end is negative + } + if (start < 0) { + start = str.length() + start; // remember start is negative + } + + // check length next + if (end > str.length()) { + end = str.length(); + } + + // if start is greater than end, return "" + if (start > end) { + return EMPTY; + } + + if (start < 0) { + start = 0; + } + if (end < 0) { + end = 0; + } + + return str.substring(start, end); + } + + // Left/Right/Mid + //----------------------------------------------------------------------- + /** + *

              Gets the leftmost {@code len} characters of a String.

              + * + *

              If {@code len} characters are not available, or the + * String is {@code null}, the String will be returned without + * an exception. An empty String is returned if len is negative.

              + * + *
              +     * StringUtils.left(null, *)    = null
              +     * StringUtils.left(*, -ve)     = ""
              +     * StringUtils.left("", *)      = ""
              +     * StringUtils.left("abc", 0)   = ""
              +     * StringUtils.left("abc", 2)   = "ab"
              +     * StringUtils.left("abc", 4)   = "abc"
              +     * 
              + * + * @param str the String to get the leftmost characters from, may be null + * @param len the length of the required String + * @return the leftmost characters, {@code null} if null String input + */ + public static String left(final String str, final int len) { + if (str == null) { + return null; + } + if (len < 0) { + return EMPTY; + } + if (str.length() <= len) { + return str; + } + return str.substring(0, len); + } + + /** + *

              Gets the rightmost {@code len} characters of a String.

              + * + *

              If {@code len} characters are not available, or the String + * is {@code null}, the String will be returned without an + * an exception. An empty String is returned if len is negative.

              + * + *
              +     * StringUtils.right(null, *)    = null
              +     * StringUtils.right(*, -ve)     = ""
              +     * StringUtils.right("", *)      = ""
              +     * StringUtils.right("abc", 0)   = ""
              +     * StringUtils.right("abc", 2)   = "bc"
              +     * StringUtils.right("abc", 4)   = "abc"
              +     * 
              + * + * @param str the String to get the rightmost characters from, may be null + * @param len the length of the required String + * @return the rightmost characters, {@code null} if null String input + */ + public static String right(final String str, final int len) { + if (str == null) { + return null; + } + if (len < 0) { + return EMPTY; + } + if (str.length() <= len) { + return str; + } + return str.substring(str.length() - len); + } + + /** + *

              Gets {@code len} characters from the middle of a String.

              + * + *

              If {@code len} characters are not available, the remainder + * of the String will be returned without an exception. If the + * String is {@code null}, {@code null} will be returned. + * An empty String is returned if len is negative or exceeds the + * length of {@code str}.

              + * + *
              +     * StringUtils.mid(null, *, *)    = null
              +     * StringUtils.mid(*, *, -ve)     = ""
              +     * StringUtils.mid("", 0, *)      = ""
              +     * StringUtils.mid("abc", 0, 2)   = "ab"
              +     * StringUtils.mid("abc", 0, 4)   = "abc"
              +     * StringUtils.mid("abc", 2, 4)   = "c"
              +     * StringUtils.mid("abc", 4, 2)   = ""
              +     * StringUtils.mid("abc", -2, 2)  = "ab"
              +     * 
              + * + * @param str the String to get the characters from, may be null + * @param pos the position to start from, negative treated as zero + * @param len the length of the required String + * @return the middle characters, {@code null} if null String input + */ + public static String mid(final String str, int pos, final int len) { + if (str == null) { + return null; + } + if (len < 0 || pos > str.length()) { + return EMPTY; + } + if (pos < 0) { + pos = 0; + } + if (str.length() <= pos + len) { + return str.substring(pos); + } + return str.substring(pos, pos + len); + } + + // SubStringAfter/SubStringBefore + //----------------------------------------------------------------------- + /** + *

              Gets the substring before the first occurrence of a separator. + * The separator is not returned.

              + * + *

              A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * A {@code null} separator will return the input string.

              + * + *

              If nothing is found, the string input is returned.

              + * + *
              +     * StringUtils.substringBefore(null, *)      = null
              +     * StringUtils.substringBefore("", *)        = ""
              +     * StringUtils.substringBefore("abc", "a")   = ""
              +     * StringUtils.substringBefore("abcba", "b") = "a"
              +     * StringUtils.substringBefore("abc", "c")   = "ab"
              +     * StringUtils.substringBefore("abc", "d")   = "abc"
              +     * StringUtils.substringBefore("abc", "")    = ""
              +     * StringUtils.substringBefore("abc", null)  = "abc"
              +     * 
              + * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring before the first occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringBefore(final String str, final String separator) { + if (isEmpty(str) || separator == null) { + return str; + } + if (separator.isEmpty()) { + return EMPTY; + } + final int pos = str.indexOf(separator); + if (pos == INDEX_NOT_FOUND) { + return str; + } + return str.substring(0, pos); + } + + /** + *

              Gets the substring after the first occurrence of a separator. + * The separator is not returned.

              + * + *

              A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * A {@code null} separator will return the empty string if the + * input string is not {@code null}.

              + * + *

              If nothing is found, the empty string is returned.

              + * + *
              +     * StringUtils.substringAfter(null, *)      = null
              +     * StringUtils.substringAfter("", *)        = ""
              +     * StringUtils.substringAfter(*, null)      = ""
              +     * StringUtils.substringAfter("abc", "a")   = "bc"
              +     * StringUtils.substringAfter("abcba", "b") = "cba"
              +     * StringUtils.substringAfter("abc", "c")   = ""
              +     * StringUtils.substringAfter("abc", "d")   = ""
              +     * StringUtils.substringAfter("abc", "")    = "abc"
              +     * 
              + * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring after the first occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringAfter(final String str, final String separator) { + if (isEmpty(str)) { + return str; + } + if (separator == null) { + return EMPTY; + } + final int pos = str.indexOf(separator); + if (pos == INDEX_NOT_FOUND) { + return EMPTY; + } + return str.substring(pos + separator.length()); + } + + /** + *

              Gets the substring before the last occurrence of a separator. + * The separator is not returned.

              + * + *

              A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * An empty or {@code null} separator will return the input string.

              + * + *

              If nothing is found, the string input is returned.

              + * + *
              +     * StringUtils.substringBeforeLast(null, *)      = null
              +     * StringUtils.substringBeforeLast("", *)        = ""
              +     * StringUtils.substringBeforeLast("abcba", "b") = "abc"
              +     * StringUtils.substringBeforeLast("abc", "c")   = "ab"
              +     * StringUtils.substringBeforeLast("a", "a")     = ""
              +     * StringUtils.substringBeforeLast("a", "z")     = "a"
              +     * StringUtils.substringBeforeLast("a", null)    = "a"
              +     * StringUtils.substringBeforeLast("a", "")      = "a"
              +     * 
              + * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring before the last occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringBeforeLast(final String str, final String separator) { + if (isEmpty(str) || isEmpty(separator)) { + return str; + } + final int pos = str.lastIndexOf(separator); + if (pos == INDEX_NOT_FOUND) { + return str; + } + return str.substring(0, pos); + } + + /** + *

              Gets the substring after the last occurrence of a separator. + * The separator is not returned.

              + * + *

              A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * An empty or {@code null} separator will return the empty string if + * the input string is not {@code null}.

              + * + *

              If nothing is found, the empty string is returned.

              + * + *
              +     * StringUtils.substringAfterLast(null, *)      = null
              +     * StringUtils.substringAfterLast("", *)        = ""
              +     * StringUtils.substringAfterLast(*, "")        = ""
              +     * StringUtils.substringAfterLast(*, null)      = ""
              +     * StringUtils.substringAfterLast("abc", "a")   = "bc"
              +     * StringUtils.substringAfterLast("abcba", "b") = "a"
              +     * StringUtils.substringAfterLast("abc", "c")   = ""
              +     * StringUtils.substringAfterLast("a", "a")     = ""
              +     * StringUtils.substringAfterLast("a", "z")     = ""
              +     * 
              + * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring after the last occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringAfterLast(final String str, final String separator) { + if (isEmpty(str)) { + return str; + } + if (isEmpty(separator)) { + return EMPTY; + } + final int pos = str.lastIndexOf(separator); + if (pos == INDEX_NOT_FOUND || pos == str.length() - separator.length()) { + return EMPTY; + } + return str.substring(pos + separator.length()); + } + + // Substring between + //----------------------------------------------------------------------- + /** + *

              Gets the String that is nested in between two instances of the + * same String.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} tag returns {@code null}.

              + * + *
              +     * StringUtils.substringBetween(null, *)            = null
              +     * StringUtils.substringBetween("", "")             = ""
              +     * StringUtils.substringBetween("", "tag")          = null
              +     * StringUtils.substringBetween("tagabctag", null)  = null
              +     * StringUtils.substringBetween("tagabctag", "")    = ""
              +     * StringUtils.substringBetween("tagabctag", "tag") = "abc"
              +     * 
              + * + * @param str the String containing the substring, may be null + * @param tag the String before and after the substring, may be null + * @return the substring, {@code null} if no match + * @since 2.0 + */ + public static String substringBetween(final String str, final String tag) { + return substringBetween(str, tag, tag); + } + + /** + *

              Gets the String that is nested in between two Strings. + * Only the first match is returned.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} open/close returns {@code null} (no match). + * An empty ("") open and close returns an empty string.

              + * + *
              +     * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
              +     * StringUtils.substringBetween(null, *, *)          = null
              +     * StringUtils.substringBetween(*, null, *)          = null
              +     * StringUtils.substringBetween(*, *, null)          = null
              +     * StringUtils.substringBetween("", "", "")          = ""
              +     * StringUtils.substringBetween("", "", "]")         = null
              +     * StringUtils.substringBetween("", "[", "]")        = null
              +     * StringUtils.substringBetween("yabcz", "", "")     = ""
              +     * StringUtils.substringBetween("yabcz", "y", "z")   = "abc"
              +     * StringUtils.substringBetween("yabczyabcz", "y", "z")   = "abc"
              +     * 
              + * + * @param str the String containing the substring, may be null + * @param open the String before the substring, may be null + * @param close the String after the substring, may be null + * @return the substring, {@code null} if no match + * @since 2.0 + */ + public static String substringBetween(final String str, final String open, final String close) { + if (str == null || open == null || close == null) { + return null; + } + final int start = str.indexOf(open); + if (start != INDEX_NOT_FOUND) { + final int end = str.indexOf(close, start + open.length()); + if (end != INDEX_NOT_FOUND) { + return str.substring(start + open.length(), end); + } + } + return null; + } + + /** + *

              Searches a String for substrings delimited by a start and end tag, + * returning all matching substrings in an array.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} open/close returns {@code null} (no match). + * An empty ("") open/close returns {@code null} (no match).

              + * + *
              +     * StringUtils.substringsBetween("[a][b][c]", "[", "]") = ["a","b","c"]
              +     * StringUtils.substringsBetween(null, *, *)            = null
              +     * StringUtils.substringsBetween(*, null, *)            = null
              +     * StringUtils.substringsBetween(*, *, null)            = null
              +     * StringUtils.substringsBetween("", "[", "]")          = []
              +     * 
              + * + * @param str the String containing the substrings, null returns null, empty returns empty + * @param open the String identifying the start of the substring, empty returns null + * @param close the String identifying the end of the substring, empty returns null + * @return a String Array of substrings, or {@code null} if no match + * @since 2.3 + */ + public static String[] substringsBetween(final String str, final String open, final String close) { + if (str == null || isEmpty(open) || isEmpty(close)) { + return null; + } + final int strLen = str.length(); + if (strLen == 0) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + final int closeLen = close.length(); + final int openLen = open.length(); + final List list = new ArrayList(); + int pos = 0; + while (pos < strLen - closeLen) { + int start = str.indexOf(open, pos); + if (start < 0) { + break; + } + start += openLen; + final int end = str.indexOf(close, start); + if (end < 0) { + break; + } + list.add(str.substring(start, end)); + pos = end + closeLen; + } + if (list.isEmpty()) { + return null; + } + return list.toArray(new String [list.size()]); + } + + // Nested extraction + //----------------------------------------------------------------------- + + // Splitting + //----------------------------------------------------------------------- + /** + *

              Splits the provided text into an array, using whitespace as the + * separator. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

              + * + *

              The separator is not included in the returned String array. + * Adjacent separators are treated as one separator. + * For more control over the split use the StrTokenizer class.

              + * + *

              A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.split(null)       = null
              +     * StringUtils.split("")         = []
              +     * StringUtils.split("abc def")  = ["abc", "def"]
              +     * StringUtils.split("abc  def") = ["abc", "def"]
              +     * StringUtils.split(" abc ")    = ["abc"]
              +     * 
              + * + * @param str the String to parse, may be null + * @return an array of parsed Strings, {@code null} if null String input + */ + public static String[] split(final String str) { + return split(str, null, -1); + } + + /** + *

              Splits the provided text into an array, separator specified. + * This is an alternative to using StringTokenizer.

              + * + *

              The separator is not included in the returned String array. + * Adjacent separators are treated as one separator. + * For more control over the split use the StrTokenizer class.

              + * + *

              A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.split(null, *)         = null
              +     * StringUtils.split("", *)           = []
              +     * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
              +     * StringUtils.split("a..b.c", '.')   = ["a", "b", "c"]
              +     * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
              +     * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
              +     * 
              + * + * @param str the String to parse, may be null + * @param separatorChar the character used as the delimiter + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.0 + */ + public static String[] split(final String str, final char separatorChar) { + return splitWorker(str, separatorChar, false); + } + + /** + *

              Splits the provided text into an array, separators specified. + * This is an alternative to using StringTokenizer.

              + * + *

              The separator is not included in the returned String array. + * Adjacent separators are treated as one separator. + * For more control over the split use the StrTokenizer class.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

              + * + *
              +     * StringUtils.split(null, *)         = null
              +     * StringUtils.split("", *)           = []
              +     * StringUtils.split("abc def", null) = ["abc", "def"]
              +     * StringUtils.split("abc def", " ")  = ["abc", "def"]
              +     * StringUtils.split("abc  def", " ") = ["abc", "def"]
              +     * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
              +     * 
              + * + * @param str the String to parse, may be null + * @param separatorChars the characters used as the delimiters, + * {@code null} splits on whitespace + * @return an array of parsed Strings, {@code null} if null String input + */ + public static String[] split(final String str, final String separatorChars) { + return splitWorker(str, separatorChars, -1, false); + } + + /** + *

              Splits the provided text into an array with a maximum length, + * separators specified.

              + * + *

              The separator is not included in the returned String array. + * Adjacent separators are treated as one separator.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

              + * + *

              If more than {@code max} delimited substrings are found, the last + * returned string includes all characters after the first {@code max - 1} + * returned strings (including separator characters).

              + * + *
              +     * StringUtils.split(null, *, *)            = null
              +     * StringUtils.split("", *, *)              = []
              +     * StringUtils.split("ab cd ef", null, 0)   = ["ab", "cd", "ef"]
              +     * StringUtils.split("ab   cd ef", null, 0) = ["ab", "cd", "ef"]
              +     * StringUtils.split("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
              +     * StringUtils.split("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
              +     * 
              + * + * @param str the String to parse, may be null + * @param separatorChars the characters used as the delimiters, + * {@code null} splits on whitespace + * @param max the maximum number of elements to include in the + * array. A zero or negative value implies no limit + * @return an array of parsed Strings, {@code null} if null String input + */ + public static String[] split(final String str, final String separatorChars, final int max) { + return splitWorker(str, separatorChars, max, false); + } + + /** + *

              Splits the provided text into an array, separator string specified.

              + * + *

              The separator(s) will not be included in the returned String array. + * Adjacent separators are treated as one separator.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

              + * + *
              +     * StringUtils.splitByWholeSeparator(null, *)               = null
              +     * StringUtils.splitByWholeSeparator("", *)                 = []
              +     * StringUtils.splitByWholeSeparator("ab de fg", null)      = ["ab", "de", "fg"]
              +     * StringUtils.splitByWholeSeparator("ab   de fg", null)    = ["ab", "de", "fg"]
              +     * StringUtils.splitByWholeSeparator("ab:cd:ef", ":")       = ["ab", "cd", "ef"]
              +     * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
              +     * 
              + * + * @param str the String to parse, may be null + * @param separator String containing the String to be used as a delimiter, + * {@code null} splits on whitespace + * @return an array of parsed Strings, {@code null} if null String was input + */ + public static String[] splitByWholeSeparator(final String str, final String separator) { + return splitByWholeSeparatorWorker( str, separator, -1, false ) ; + } + + /** + *

              Splits the provided text into an array, separator string specified. + * Returns a maximum of {@code max} substrings.

              + * + *

              The separator(s) will not be included in the returned String array. + * Adjacent separators are treated as one separator.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

              + * + *
              +     * StringUtils.splitByWholeSeparator(null, *, *)               = null
              +     * StringUtils.splitByWholeSeparator("", *, *)                 = []
              +     * StringUtils.splitByWholeSeparator("ab de fg", null, 0)      = ["ab", "de", "fg"]
              +     * StringUtils.splitByWholeSeparator("ab   de fg", null, 0)    = ["ab", "de", "fg"]
              +     * StringUtils.splitByWholeSeparator("ab:cd:ef", ":", 2)       = ["ab", "cd:ef"]
              +     * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-", 5) = ["ab", "cd", "ef"]
              +     * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-", 2) = ["ab", "cd-!-ef"]
              +     * 
              + * + * @param str the String to parse, may be null + * @param separator String containing the String to be used as a delimiter, + * {@code null} splits on whitespace + * @param max the maximum number of elements to include in the returned + * array. A zero or negative value implies no limit. + * @return an array of parsed Strings, {@code null} if null String was input + */ + public static String[] splitByWholeSeparator( final String str, final String separator, final int max ) { + return splitByWholeSeparatorWorker(str, separator, max, false); + } + + /** + *

              Splits the provided text into an array, separator string specified.

              + * + *

              The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

              + * + *
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens(null, *)               = null
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("", *)                 = []
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null)      = ["ab", "de", "fg"]
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab   de fg", null)    = ["ab", "", "", "de", "fg"]
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab:cd:ef", ":")       = ["ab", "cd", "ef"]
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
              +     * 
              + * + * @param str the String to parse, may be null + * @param separator String containing the String to be used as a delimiter, + * {@code null} splits on whitespace + * @return an array of parsed Strings, {@code null} if null String was input + * @since 2.4 + */ + public static String[] splitByWholeSeparatorPreserveAllTokens(final String str, final String separator) { + return splitByWholeSeparatorWorker(str, separator, -1, true); + } + + /** + *

              Splits the provided text into an array, separator string specified. + * Returns a maximum of {@code max} substrings.

              + * + *

              The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

              + * + *
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens(null, *, *)               = null
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("", *, *)                 = []
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null, 0)      = ["ab", "de", "fg"]
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab   de fg", null, 0)    = ["ab", "", "", "de", "fg"]
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab:cd:ef", ":", 2)       = ["ab", "cd:ef"]
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-", 5) = ["ab", "cd", "ef"]
              +     * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-", 2) = ["ab", "cd-!-ef"]
              +     * 
              + * + * @param str the String to parse, may be null + * @param separator String containing the String to be used as a delimiter, + * {@code null} splits on whitespace + * @param max the maximum number of elements to include in the returned + * array. A zero or negative value implies no limit. + * @return an array of parsed Strings, {@code null} if null String was input + * @since 2.4 + */ + public static String[] splitByWholeSeparatorPreserveAllTokens(final String str, final String separator, final int max) { + return splitByWholeSeparatorWorker(str, separator, max, true); + } + + /** + * Performs the logic for the {@code splitByWholeSeparatorPreserveAllTokens} methods. + * + * @param str the String to parse, may be {@code null} + * @param separator String containing the String to be used as a delimiter, + * {@code null} splits on whitespace + * @param max the maximum number of elements to include in the returned + * array. A zero or negative value implies no limit. + * @param preserveAllTokens if {@code true}, adjacent separators are + * treated as empty token separators; if {@code false}, adjacent + * separators are treated as one separator. + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.4 + */ + private static String[] splitByWholeSeparatorWorker( + final String str, final String separator, final int max, final boolean preserveAllTokens) { + if (str == null) { + return null; + } + + final int len = str.length(); + + if (len == 0) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + + if (separator == null || EMPTY.equals(separator)) { + // Split on whitespace. + return splitWorker(str, null, max, preserveAllTokens); + } + + final int separatorLength = separator.length(); + + final ArrayList substrings = new ArrayList(); + int numberOfSubstrings = 0; + int beg = 0; + int end = 0; + while (end < len) { + end = str.indexOf(separator, beg); + + if (end > -1) { + if (end > beg) { + numberOfSubstrings += 1; + + if (numberOfSubstrings == max) { + end = len; + substrings.add(str.substring(beg)); + } else { + // The following is OK, because String.substring( beg, end ) excludes + // the character at the position 'end'. + substrings.add(str.substring(beg, end)); + + // Set the starting point for the next search. + // The following is equivalent to beg = end + (separatorLength - 1) + 1, + // which is the right calculation: + beg = end + separatorLength; + } + } else { + // We found a consecutive occurrence of the separator, so skip it. + if (preserveAllTokens) { + numberOfSubstrings += 1; + if (numberOfSubstrings == max) { + end = len; + substrings.add(str.substring(beg)); + } else { + substrings.add(EMPTY); + } + } + beg = end + separatorLength; + } + } else { + // String.substring( beg ) goes from 'beg' to the end of the String. + substrings.add(str.substring(beg)); + end = len; + } + } + + return substrings.toArray(new String[substrings.size()]); + } + + // ----------------------------------------------------------------------- + /** + *

              Splits the provided text into an array, using whitespace as the + * separator, preserving all tokens, including empty tokens created by + * adjacent separators. This is an alternative to using StringTokenizer. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

              + * + *

              The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.

              + * + *

              A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.splitPreserveAllTokens(null)       = null
              +     * StringUtils.splitPreserveAllTokens("")         = []
              +     * StringUtils.splitPreserveAllTokens("abc def")  = ["abc", "def"]
              +     * StringUtils.splitPreserveAllTokens("abc  def") = ["abc", "", "def"]
              +     * StringUtils.splitPreserveAllTokens(" abc ")    = ["", "abc", ""]
              +     * 
              + * + * @param str the String to parse, may be {@code null} + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.1 + */ + public static String[] splitPreserveAllTokens(final String str) { + return splitWorker(str, null, -1, true); + } + + /** + *

              Splits the provided text into an array, separator specified, + * preserving all tokens, including empty tokens created by adjacent + * separators. This is an alternative to using StringTokenizer.

              + * + *

              The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.

              + * + *

              A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.splitPreserveAllTokens(null, *)         = null
              +     * StringUtils.splitPreserveAllTokens("", *)           = []
              +     * StringUtils.splitPreserveAllTokens("a.b.c", '.')    = ["a", "b", "c"]
              +     * StringUtils.splitPreserveAllTokens("a..b.c", '.')   = ["a", "", "b", "c"]
              +     * StringUtils.splitPreserveAllTokens("a:b:c", '.')    = ["a:b:c"]
              +     * StringUtils.splitPreserveAllTokens("a\tb\nc", null) = ["a", "b", "c"]
              +     * StringUtils.splitPreserveAllTokens("a b c", ' ')    = ["a", "b", "c"]
              +     * StringUtils.splitPreserveAllTokens("a b c ", ' ')   = ["a", "b", "c", ""]
              +     * StringUtils.splitPreserveAllTokens("a b c  ", ' ')   = ["a", "b", "c", "", ""]
              +     * StringUtils.splitPreserveAllTokens(" a b c", ' ')   = ["", a", "b", "c"]
              +     * StringUtils.splitPreserveAllTokens("  a b c", ' ')  = ["", "", a", "b", "c"]
              +     * StringUtils.splitPreserveAllTokens(" a b c ", ' ')  = ["", a", "b", "c", ""]
              +     * 
              + * + * @param str the String to parse, may be {@code null} + * @param separatorChar the character used as the delimiter, + * {@code null} splits on whitespace + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.1 + */ + public static String[] splitPreserveAllTokens(final String str, final char separatorChar) { + return splitWorker(str, separatorChar, true); + } + + /** + * Performs the logic for the {@code split} and + * {@code splitPreserveAllTokens} methods that do not return a + * maximum array length. + * + * @param str the String to parse, may be {@code null} + * @param separatorChar the separate character + * @param preserveAllTokens if {@code true}, adjacent separators are + * treated as empty token separators; if {@code false}, adjacent + * separators are treated as one separator. + * @return an array of parsed Strings, {@code null} if null String input + */ + private static String[] splitWorker(final String str, final char separatorChar, final boolean preserveAllTokens) { + // Performance tuned for 2.0 (JDK1.4) + + if (str == null) { + return null; + } + final int len = str.length(); + if (len == 0) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + final List list = new ArrayList(); + int i = 0, start = 0; + boolean match = false; + boolean lastMatch = false; + while (i < len) { + if (str.charAt(i) == separatorChar) { + if (match || preserveAllTokens) { + list.add(str.substring(start, i)); + match = false; + lastMatch = true; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + if (match || preserveAllTokens && lastMatch) { + list.add(str.substring(start, i)); + } + return list.toArray(new String[list.size()]); + } + + /** + *

              Splits the provided text into an array, separators specified, + * preserving all tokens, including empty tokens created by adjacent + * separators. This is an alternative to using StringTokenizer.

              + * + *

              The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

              + * + *
              +     * StringUtils.splitPreserveAllTokens(null, *)           = null
              +     * StringUtils.splitPreserveAllTokens("", *)             = []
              +     * StringUtils.splitPreserveAllTokens("abc def", null)   = ["abc", "def"]
              +     * StringUtils.splitPreserveAllTokens("abc def", " ")    = ["abc", "def"]
              +     * StringUtils.splitPreserveAllTokens("abc  def", " ")   = ["abc", "", def"]
              +     * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":")   = ["ab", "cd", "ef"]
              +     * StringUtils.splitPreserveAllTokens("ab:cd:ef:", ":")  = ["ab", "cd", "ef", ""]
              +     * StringUtils.splitPreserveAllTokens("ab:cd:ef::", ":") = ["ab", "cd", "ef", "", ""]
              +     * StringUtils.splitPreserveAllTokens("ab::cd:ef", ":")  = ["ab", "", cd", "ef"]
              +     * StringUtils.splitPreserveAllTokens(":cd:ef", ":")     = ["", cd", "ef"]
              +     * StringUtils.splitPreserveAllTokens("::cd:ef", ":")    = ["", "", cd", "ef"]
              +     * StringUtils.splitPreserveAllTokens(":cd:ef:", ":")    = ["", cd", "ef", ""]
              +     * 
              + * + * @param str the String to parse, may be {@code null} + * @param separatorChars the characters used as the delimiters, + * {@code null} splits on whitespace + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.1 + */ + public static String[] splitPreserveAllTokens(final String str, final String separatorChars) { + return splitWorker(str, separatorChars, -1, true); + } + + /** + *

              Splits the provided text into an array with a maximum length, + * separators specified, preserving all tokens, including empty tokens + * created by adjacent separators.

              + * + *

              The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * Adjacent separators are treated as one separator.

              + * + *

              A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

              + * + *

              If more than {@code max} delimited substrings are found, the last + * returned string includes all characters after the first {@code max - 1} + * returned strings (including separator characters).

              + * + *
              +     * StringUtils.splitPreserveAllTokens(null, *, *)            = null
              +     * StringUtils.splitPreserveAllTokens("", *, *)              = []
              +     * StringUtils.splitPreserveAllTokens("ab de fg", null, 0)   = ["ab", "cd", "ef"]
              +     * StringUtils.splitPreserveAllTokens("ab   de fg", null, 0) = ["ab", "cd", "ef"]
              +     * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
              +     * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
              +     * StringUtils.splitPreserveAllTokens("ab   de fg", null, 2) = ["ab", "  de fg"]
              +     * StringUtils.splitPreserveAllTokens("ab   de fg", null, 3) = ["ab", "", " de fg"]
              +     * StringUtils.splitPreserveAllTokens("ab   de fg", null, 4) = ["ab", "", "", "de fg"]
              +     * 
              + * + * @param str the String to parse, may be {@code null} + * @param separatorChars the characters used as the delimiters, + * {@code null} splits on whitespace + * @param max the maximum number of elements to include in the + * array. A zero or negative value implies no limit + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.1 + */ + public static String[] splitPreserveAllTokens(final String str, final String separatorChars, final int max) { + return splitWorker(str, separatorChars, max, true); + } + + /** + * Performs the logic for the {@code split} and + * {@code splitPreserveAllTokens} methods that return a maximum array + * length. + * + * @param str the String to parse, may be {@code null} + * @param separatorChars the separate character + * @param max the maximum number of elements to include in the + * array. A zero or negative value implies no limit. + * @param preserveAllTokens if {@code true}, adjacent separators are + * treated as empty token separators; if {@code false}, adjacent + * separators are treated as one separator. + * @return an array of parsed Strings, {@code null} if null String input + */ + private static String[] splitWorker(final String str, final String separatorChars, final int max, final boolean preserveAllTokens) { + // Performance tuned for 2.0 (JDK1.4) + // Direct code is quicker than StringTokenizer. + // Also, StringTokenizer uses isSpace() not isWhitespace() + + if (str == null) { + return null; + } + final int len = str.length(); + if (len == 0) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + final List list = new ArrayList(); + int sizePlus1 = 1; + int i = 0, start = 0; + boolean match = false; + boolean lastMatch = false; + if (separatorChars == null) { + // Null separator means use whitespace + while (i < len) { + if (Character.isWhitespace(str.charAt(i))) { + if (match || preserveAllTokens) { + lastMatch = true; + if (sizePlus1++ == max) { + i = len; + lastMatch = false; + } + list.add(str.substring(start, i)); + match = false; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + } else if (separatorChars.length() == 1) { + // Optimise 1 character case + final char sep = separatorChars.charAt(0); + while (i < len) { + if (str.charAt(i) == sep) { + if (match || preserveAllTokens) { + lastMatch = true; + if (sizePlus1++ == max) { + i = len; + lastMatch = false; + } + list.add(str.substring(start, i)); + match = false; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + } else { + // standard case + while (i < len) { + if (separatorChars.indexOf(str.charAt(i)) >= 0) { + if (match || preserveAllTokens) { + lastMatch = true; + if (sizePlus1++ == max) { + i = len; + lastMatch = false; + } + list.add(str.substring(start, i)); + match = false; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + } + if (match || preserveAllTokens && lastMatch) { + list.add(str.substring(start, i)); + } + return list.toArray(new String[list.size()]); + } + + /** + *

              Splits a String by Character type as returned by + * {@code java.lang.Character.getType(char)}. Groups of contiguous + * characters of the same type are returned as complete tokens. + *

              +     * StringUtils.splitByCharacterType(null)         = null
              +     * StringUtils.splitByCharacterType("")           = []
              +     * StringUtils.splitByCharacterType("ab de fg")   = ["ab", " ", "de", " ", "fg"]
              +     * StringUtils.splitByCharacterType("ab   de fg") = ["ab", "   ", "de", " ", "fg"]
              +     * StringUtils.splitByCharacterType("ab:cd:ef")   = ["ab", ":", "cd", ":", "ef"]
              +     * StringUtils.splitByCharacterType("number5")    = ["number", "5"]
              +     * StringUtils.splitByCharacterType("fooBar")     = ["foo", "B", "ar"]
              +     * StringUtils.splitByCharacterType("foo200Bar")  = ["foo", "200", "B", "ar"]
              +     * StringUtils.splitByCharacterType("ASFRules")   = ["ASFR", "ules"]
              +     * 
              + * @param str the String to split, may be {@code null} + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.4 + */ + public static String[] splitByCharacterType(final String str) { + return splitByCharacterType(str, false); + } + + /** + *

              Splits a String by Character type as returned by + * {@code java.lang.Character.getType(char)}. Groups of contiguous + * characters of the same type are returned as complete tokens, with the + * following exception: the character of type + * {@code Character.UPPERCASE_LETTER}, if any, immediately + * preceding a token of type {@code Character.LOWERCASE_LETTER} + * will belong to the following token rather than to the preceding, if any, + * {@code Character.UPPERCASE_LETTER} token. + *

              +     * StringUtils.splitByCharacterTypeCamelCase(null)         = null
              +     * StringUtils.splitByCharacterTypeCamelCase("")           = []
              +     * StringUtils.splitByCharacterTypeCamelCase("ab de fg")   = ["ab", " ", "de", " ", "fg"]
              +     * StringUtils.splitByCharacterTypeCamelCase("ab   de fg") = ["ab", "   ", "de", " ", "fg"]
              +     * StringUtils.splitByCharacterTypeCamelCase("ab:cd:ef")   = ["ab", ":", "cd", ":", "ef"]
              +     * StringUtils.splitByCharacterTypeCamelCase("number5")    = ["number", "5"]
              +     * StringUtils.splitByCharacterTypeCamelCase("fooBar")     = ["foo", "Bar"]
              +     * StringUtils.splitByCharacterTypeCamelCase("foo200Bar")  = ["foo", "200", "Bar"]
              +     * StringUtils.splitByCharacterTypeCamelCase("ASFRules")   = ["ASF", "Rules"]
              +     * 
              + * @param str the String to split, may be {@code null} + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.4 + */ + public static String[] splitByCharacterTypeCamelCase(final String str) { + return splitByCharacterType(str, true); + } + + /** + *

              Splits a String by Character type as returned by + * {@code java.lang.Character.getType(char)}. Groups of contiguous + * characters of the same type are returned as complete tokens, with the + * following exception: if {@code camelCase} is {@code true}, + * the character of type {@code Character.UPPERCASE_LETTER}, if any, + * immediately preceding a token of type {@code Character.LOWERCASE_LETTER} + * will belong to the following token rather than to the preceding, if any, + * {@code Character.UPPERCASE_LETTER} token. + * @param str the String to split, may be {@code null} + * @param camelCase whether to use so-called "camel-case" for letter types + * @return an array of parsed Strings, {@code null} if null String input + * @since 2.4 + */ + private static String[] splitByCharacterType(final String str, final boolean camelCase) { + if (str == null) { + return null; + } + if (str.isEmpty()) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + final char[] c = str.toCharArray(); + final List list = new ArrayList(); + int tokenStart = 0; + int currentType = Character.getType(c[tokenStart]); + for (int pos = tokenStart + 1; pos < c.length; pos++) { + final int type = Character.getType(c[pos]); + if (type == currentType) { + continue; + } + if (camelCase && type == Character.LOWERCASE_LETTER && currentType == Character.UPPERCASE_LETTER) { + final int newTokenStart = pos - 1; + if (newTokenStart != tokenStart) { + list.add(new String(c, tokenStart, newTokenStart - tokenStart)); + tokenStart = newTokenStart; + } + } else { + list.add(new String(c, tokenStart, pos - tokenStart)); + tokenStart = pos; + } + currentType = type; + } + list.add(new String(c, tokenStart, c.length - tokenStart)); + return list.toArray(new String[list.size()]); + } + + // Joining + //----------------------------------------------------------------------- + /** + *

              Joins the elements of the provided array into a single String + * containing the provided list of elements.

              + * + *

              No separator is added to the joined String. + * Null objects or empty strings within the array are represented by + * empty strings.

              + * + *
              +     * StringUtils.join(null)            = null
              +     * StringUtils.join([])              = ""
              +     * StringUtils.join([null])          = ""
              +     * StringUtils.join(["a", "b", "c"]) = "abc"
              +     * StringUtils.join([null, "", "a"]) = "a"
              +     * 
              + * + * @param the specific type of values to join together + * @param elements the values to join together, may be null + * @return the joined String, {@code null} if null array input + * @since 2.0 + * @since 3.0 Changed signature to use varargs + */ + public static String join(final T... elements) { + return join(elements, null); + } + + /** + *

              Joins the elements of the provided array into a single String + * containing the provided list of elements.

              + * + *

              No delimiter is added before or after the list. + * Null objects or empty strings within the array are represented by + * empty strings.

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
              +     * StringUtils.join(["a", "b", "c"], null) = "abc"
              +     * StringUtils.join([null, "", "a"], ';')  = ";;a"
              +     * 
              + * + * @param array the array of values to join together, may be null + * @param separator the separator character to use + * @return the joined String, {@code null} if null array input + * @since 2.0 + */ + public static String join(final Object[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final long[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final int[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final short[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final byte[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final char[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final float[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final double[] array, final char separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + + /** + *

              Joins the elements of the provided array into a single String + * containing the provided list of elements.

              + * + *

              No delimiter is added before or after the list. + * Null objects or empty strings within the array are represented by + * empty strings.

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
              +     * StringUtils.join(["a", "b", "c"], null) = "abc"
              +     * StringUtils.join([null, "", "a"], ';')  = ";;a"
              +     * 
              + * + * @param array the array of values to join together, may be null + * @param separator the separator character to use + * @param startIndex the first index to start joining from. It is + * an error to pass in an end index past the end of the array + * @param endIndex the index to stop joining from (exclusive). It is + * an error to pass in an end index past the end of the array + * @return the joined String, {@code null} if null array input + * @since 2.0 + */ + public static String join(final Object[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + if (array[i] != null) { + buf.append(array[i]); + } + } + return buf.toString(); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final long[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final int[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final byte[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final short[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final char[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final double[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + /** + *

              + * Joins the elements of the provided array into a single String containing the provided list of elements. + *

              + * + *

              + * No delimiter is added before or after the list. Null objects or empty strings within the array are represented + * by empty strings. + *

              + * + *
              +     * StringUtils.join(null, *)               = null
              +     * StringUtils.join([], *)                 = ""
              +     * StringUtils.join([null], *)             = ""
              +     * StringUtils.join([1, 2, 3], ';')  = "1;2;3"
              +     * StringUtils.join([1, 2, 3], null) = "123"
              +     * 
              + * + * @param array + * the array of values to join together, may be null + * @param separator + * the separator character to use + * @param startIndex + * the first index to start joining from. It is an error to pass in an end index past the end of the + * array + * @param endIndex + * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of + * the array + * @return the joined String, {@code null} if null array input + * @since 3.2 + */ + public static String join(final float[] array, final char separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + buf.append(array[i]); + } + return buf.toString(); + } + + + /** + *

              Joins the elements of the provided array into a single String + * containing the provided list of elements.

              + * + *

              No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String (""). + * Null objects or empty strings within the array are represented by + * empty strings.

              + * + *
              +     * StringUtils.join(null, *)                = null
              +     * StringUtils.join([], *)                  = ""
              +     * StringUtils.join([null], *)              = ""
              +     * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
              +     * StringUtils.join(["a", "b", "c"], null)  = "abc"
              +     * StringUtils.join(["a", "b", "c"], "")    = "abc"
              +     * StringUtils.join([null, "", "a"], ',')   = ",,a"
              +     * 
              + * + * @param array the array of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @return the joined String, {@code null} if null array input + */ + public static String join(final Object[] array, final String separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

              Joins the elements of the provided array into a single String + * containing the provided list of elements.

              + * + *

              No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String (""). + * Null objects or empty strings within the array are represented by + * empty strings.

              + * + *
              +     * StringUtils.join(null, *, *, *)                = null
              +     * StringUtils.join([], *, *, *)                  = ""
              +     * StringUtils.join([null], *, *, *)              = ""
              +     * StringUtils.join(["a", "b", "c"], "--", 0, 3)  = "a--b--c"
              +     * StringUtils.join(["a", "b", "c"], "--", 1, 3)  = "b--c"
              +     * StringUtils.join(["a", "b", "c"], "--", 2, 3)  = "c"
              +     * StringUtils.join(["a", "b", "c"], "--", 2, 2)  = ""
              +     * StringUtils.join(["a", "b", "c"], null, 0, 3)  = "abc"
              +     * StringUtils.join(["a", "b", "c"], "", 0, 3)    = "abc"
              +     * StringUtils.join([null, "", "a"], ',', 0, 3)   = ",,a"
              +     * 
              + * + * @param array the array of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @param startIndex the first index to start joining from. + * @param endIndex the index to stop joining from (exclusive). + * @return the joined String, {@code null} if null array input; or the empty string + * if {@code endIndex - startIndex <= 0}. The number of joined entries is given by + * {@code endIndex - startIndex} + * @throws ArrayIndexOutOfBoundsException ife
              + * {@code startIndex < 0} or
              + * {@code startIndex >= array.length()} or
              + * {@code endIndex < 0} or
              + * {@code endIndex > array.length()} + */ + public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) { + if (array == null) { + return null; + } + if (separator == null) { + separator = EMPTY; + } + + // endIndex - startIndex > 0: Len = NofStrings *(len(firstString) + len(separator)) + // (Assuming that all Strings are roughly equally long) + final int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) { + return EMPTY; + } + + final StringBuilder buf = new StringBuilder(noOfItems * 16); + + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + if (array[i] != null) { + buf.append(array[i]); + } + } + return buf.toString(); + } + + /** + *

              Joins the elements of the provided {@code Iterator} into + * a single String containing the provided elements.

              + * + *

              No delimiter is added before or after the list. Null objects or empty + * strings within the iteration are represented by empty strings.

              + * + *

              See the examples here: {@link #join(Object[],char)}.

              + * + * @param iterator the {@code Iterator} of values to join together, may be null + * @param separator the separator character to use + * @return the joined String, {@code null} if null iterator input + * @since 2.0 + */ + public static String join(final Iterator iterator, final char separator) { + + // handle null, zero and one elements before building a buffer + if (iterator == null) { + return null; + } + if (!iterator.hasNext()) { + return EMPTY; + } + final Object first = iterator.next(); + if (!iterator.hasNext()) { + @SuppressWarnings( "deprecation" ) // ObjectUtils.toString(Object) has been deprecated in 3.2 + final + String result = ObjectUtils.toString(first); + return result; + } + + // two or more elements + final StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small + if (first != null) { + buf.append(first); + } + + while (iterator.hasNext()) { + buf.append(separator); + final Object obj = iterator.next(); + if (obj != null) { + buf.append(obj); + } + } + + return buf.toString(); + } + + /** + *

              Joins the elements of the provided {@code Iterator} into + * a single String containing the provided elements.

              + * + *

              No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String ("").

              + * + *

              See the examples here: {@link #join(Object[],String)}.

              + * + * @param iterator the {@code Iterator} of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @return the joined String, {@code null} if null iterator input + */ + public static String join(final Iterator iterator, final String separator) { + + // handle null, zero and one elements before building a buffer + if (iterator == null) { + return null; + } + if (!iterator.hasNext()) { + return EMPTY; + } + final Object first = iterator.next(); + if (!iterator.hasNext()) { + @SuppressWarnings( "deprecation" ) // ObjectUtils.toString(Object) has been deprecated in 3.2 + final String result = ObjectUtils.toString(first); + return result; + } + + // two or more elements + final StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small + if (first != null) { + buf.append(first); + } + + while (iterator.hasNext()) { + if (separator != null) { + buf.append(separator); + } + final Object obj = iterator.next(); + if (obj != null) { + buf.append(obj); + } + } + return buf.toString(); + } + + /** + *

              Joins the elements of the provided {@code Iterable} into + * a single String containing the provided elements.

              + * + *

              No delimiter is added before or after the list. Null objects or empty + * strings within the iteration are represented by empty strings.

              + * + *

              See the examples here: {@link #join(Object[],char)}.

              + * + * @param iterable the {@code Iterable} providing the values to join together, may be null + * @param separator the separator character to use + * @return the joined String, {@code null} if null iterator input + * @since 2.3 + */ + public static String join(final Iterable iterable, final char separator) { + if (iterable == null) { + return null; + } + return join(iterable.iterator(), separator); + } + + /** + *

              Joins the elements of the provided {@code Iterable} into + * a single String containing the provided elements.

              + * + *

              No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String ("").

              + * + *

              See the examples here: {@link #join(Object[],String)}.

              + * + * @param iterable the {@code Iterable} providing the values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @return the joined String, {@code null} if null iterator input + * @since 2.3 + */ + public static String join(final Iterable iterable, final String separator) { + if (iterable == null) { + return null; + } + return join(iterable.iterator(), separator); + } + + // Delete + //----------------------------------------------------------------------- + /** + *

              Deletes all whitespaces from a String as defined by + * {@link Character#isWhitespace(char)}.

              + * + *
              +     * StringUtils.deleteWhitespace(null)         = null
              +     * StringUtils.deleteWhitespace("")           = ""
              +     * StringUtils.deleteWhitespace("abc")        = "abc"
              +     * StringUtils.deleteWhitespace("   ab  c  ") = "abc"
              +     * 
              + * + * @param str the String to delete whitespace from, may be null + * @return the String without whitespaces, {@code null} if null String input + */ + public static String deleteWhitespace(final String str) { + if (isEmpty(str)) { + return str; + } + final int sz = str.length(); + final char[] chs = new char[sz]; + int count = 0; + for (int i = 0; i < sz; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + chs[count++] = str.charAt(i); + } + } + if (count == sz) { + return str; + } + return new String(chs, 0, count); + } + + // Remove + //----------------------------------------------------------------------- + /** + *

              Removes a substring only if it is at the beginning of a source string, + * otherwise returns the source string.

              + * + *

              A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

              + * + *
              +     * StringUtils.removeStart(null, *)      = null
              +     * StringUtils.removeStart("", *)        = ""
              +     * StringUtils.removeStart(*, null)      = *
              +     * StringUtils.removeStart("www.domain.com", "www.")   = "domain.com"
              +     * StringUtils.removeStart("domain.com", "www.")       = "domain.com"
              +     * StringUtils.removeStart("www.domain.com", "domain") = "www.domain.com"
              +     * StringUtils.removeStart("abc", "")    = "abc"
              +     * 
              + * + * @param str the source String to search, may be null + * @param remove the String to search for and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String removeStart(final String str, final String remove) { + if (isEmpty(str) || isEmpty(remove)) { + return str; + } + if (str.startsWith(remove)){ + return str.substring(remove.length()); + } + return str; + } + + /** + *

              Case insensitive removal of a substring if it is at the beginning of a source string, + * otherwise returns the source string.

              + * + *

              A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

              + * + *
              +     * StringUtils.removeStartIgnoreCase(null, *)      = null
              +     * StringUtils.removeStartIgnoreCase("", *)        = ""
              +     * StringUtils.removeStartIgnoreCase(*, null)      = *
              +     * StringUtils.removeStartIgnoreCase("www.domain.com", "www.")   = "domain.com"
              +     * StringUtils.removeStartIgnoreCase("www.domain.com", "WWW.")   = "domain.com"
              +     * StringUtils.removeStartIgnoreCase("domain.com", "www.")       = "domain.com"
              +     * StringUtils.removeStartIgnoreCase("www.domain.com", "domain") = "www.domain.com"
              +     * StringUtils.removeStartIgnoreCase("abc", "")    = "abc"
              +     * 
              + * + * @param str the source String to search, may be null + * @param remove the String to search for (case insensitive) and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.4 + */ + public static String removeStartIgnoreCase(final String str, final String remove) { + if (isEmpty(str) || isEmpty(remove)) { + return str; + } + if (startsWithIgnoreCase(str, remove)) { + return str.substring(remove.length()); + } + return str; + } + + /** + *

              Removes a substring only if it is at the end of a source string, + * otherwise returns the source string.

              + * + *

              A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

              + * + *
              +     * StringUtils.removeEnd(null, *)      = null
              +     * StringUtils.removeEnd("", *)        = ""
              +     * StringUtils.removeEnd(*, null)      = *
              +     * StringUtils.removeEnd("www.domain.com", ".com.")  = "www.domain.com"
              +     * StringUtils.removeEnd("www.domain.com", ".com")   = "www.domain"
              +     * StringUtils.removeEnd("www.domain.com", "domain") = "www.domain.com"
              +     * StringUtils.removeEnd("abc", "")    = "abc"
              +     * 
              + * + * @param str the source String to search, may be null + * @param remove the String to search for and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String removeEnd(final String str, final String remove) { + if (isEmpty(str) || isEmpty(remove)) { + return str; + } + if (str.endsWith(remove)) { + return str.substring(0, str.length() - remove.length()); + } + return str; + } + + /** + *

              Case insensitive removal of a substring if it is at the end of a source string, + * otherwise returns the source string.

              + * + *

              A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

              + * + *
              +     * StringUtils.removeEndIgnoreCase(null, *)      = null
              +     * StringUtils.removeEndIgnoreCase("", *)        = ""
              +     * StringUtils.removeEndIgnoreCase(*, null)      = *
              +     * StringUtils.removeEndIgnoreCase("www.domain.com", ".com.")  = "www.domain.com"
              +     * StringUtils.removeEndIgnoreCase("www.domain.com", ".com")   = "www.domain"
              +     * StringUtils.removeEndIgnoreCase("www.domain.com", "domain") = "www.domain.com"
              +     * StringUtils.removeEndIgnoreCase("abc", "")    = "abc"
              +     * StringUtils.removeEndIgnoreCase("www.domain.com", ".COM") = "www.domain")
              +     * StringUtils.removeEndIgnoreCase("www.domain.COM", ".com") = "www.domain")
              +     * 
              + * + * @param str the source String to search, may be null + * @param remove the String to search for (case insensitive) and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.4 + */ + public static String removeEndIgnoreCase(final String str, final String remove) { + if (isEmpty(str) || isEmpty(remove)) { + return str; + } + if (endsWithIgnoreCase(str, remove)) { + return str.substring(0, str.length() - remove.length()); + } + return str; + } + + /** + *

              Removes all occurrences of a substring from within the source string.

              + * + *

              A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} remove string will return the source string. + * An empty ("") remove string will return the source string.

              + * + *
              +     * StringUtils.remove(null, *)        = null
              +     * StringUtils.remove("", *)          = ""
              +     * StringUtils.remove(*, null)        = *
              +     * StringUtils.remove(*, "")          = *
              +     * StringUtils.remove("queued", "ue") = "qd"
              +     * StringUtils.remove("queued", "zz") = "queued"
              +     * 
              + * + * @param str the source String to search, may be null + * @param remove the String to search for and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String remove(final String str, final String remove) { + if (isEmpty(str) || isEmpty(remove)) { + return str; + } + return replace(str, remove, EMPTY, -1); + } + + /** + *

              Removes all occurrences of a character from within the source string.

              + * + *

              A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string.

              + * + *
              +     * StringUtils.remove(null, *)       = null
              +     * StringUtils.remove("", *)         = ""
              +     * StringUtils.remove("queued", 'u') = "qeed"
              +     * StringUtils.remove("queued", 'z') = "queued"
              +     * 
              + * + * @param str the source String to search, may be null + * @param remove the char to search for and remove, may be null + * @return the substring with the char removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String remove(final String str, final char remove) { + if (isEmpty(str) || str.indexOf(remove) == INDEX_NOT_FOUND) { + return str; + } + final char[] chars = str.toCharArray(); + int pos = 0; + for (int i = 0; i < chars.length; i++) { + if (chars[i] != remove) { + chars[pos++] = chars[i]; + } + } + return new String(chars, 0, pos); + } + + // Replacing + //----------------------------------------------------------------------- + /** + *

              Replaces a String with another String inside a larger String, once.

              + * + *

              A {@code null} reference passed to this method is a no-op.

              + * + *
              +     * StringUtils.replaceOnce(null, *, *)        = null
              +     * StringUtils.replaceOnce("", *, *)          = ""
              +     * StringUtils.replaceOnce("any", null, *)    = "any"
              +     * StringUtils.replaceOnce("any", *, null)    = "any"
              +     * StringUtils.replaceOnce("any", "", *)      = "any"
              +     * StringUtils.replaceOnce("aba", "a", null)  = "aba"
              +     * StringUtils.replaceOnce("aba", "a", "")    = "ba"
              +     * StringUtils.replaceOnce("aba", "a", "z")   = "zba"
              +     * 
              + * + * @see #replace(String text, String searchString, String replacement, int max) + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null + * @param replacement the String to replace with, may be null + * @return the text with any replacements processed, + * {@code null} if null String input + */ + public static String replaceOnce(final String text, final String searchString, final String replacement) { + return replace(text, searchString, replacement, 1); + } + + /** + * Replaces each substring of the source String that matches the given regular expression with the given + * replacement using the {@link Pattern#DOTALL} option. DOTALL is also know as single-line mode in Perl. This call + * is also equivalent to: + *
                + *
              • {@code source.replaceAll("(?s)" + regex, replacement)}
              • + *
              • {@code Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(replacement)}
              • + *
              + * + * @param source + * the source string + * @param regex + * the regular expression to which this string is to be matched + * @param replacement + * the string to be substituted for each match + * @return The resulting {@code String} + * @see String#replaceAll(String, String) + * @see Pattern#DOTALL + * @since 3.2 + */ + public static String replacePattern(final String source, final String regex, final String replacement) { + return Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(replacement); + } + + /** + * Removes each substring of the source String that matches the given regular expression using the DOTALL option. + * + * @param source + * the source string + * @param regex + * the regular expression to which this string is to be matched + * @return The resulting {@code String} + * @see String#replaceAll(String, String) + * @see Pattern#DOTALL + * @since 3.2 + */ + public static String removePattern(final String source, final String regex) { + return replacePattern(source, regex, StringUtils.EMPTY); + } + + /** + *

              Replaces all occurrences of a String within another String.

              + * + *

              A {@code null} reference passed to this method is a no-op.

              + * + *
              +     * StringUtils.replace(null, *, *)        = null
              +     * StringUtils.replace("", *, *)          = ""
              +     * StringUtils.replace("any", null, *)    = "any"
              +     * StringUtils.replace("any", *, null)    = "any"
              +     * StringUtils.replace("any", "", *)      = "any"
              +     * StringUtils.replace("aba", "a", null)  = "aba"
              +     * StringUtils.replace("aba", "a", "")    = "b"
              +     * StringUtils.replace("aba", "a", "z")   = "zbz"
              +     * 
              + * + * @see #replace(String text, String searchString, String replacement, int max) + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null + * @param replacement the String to replace it with, may be null + * @return the text with any replacements processed, + * {@code null} if null String input + */ + public static String replace(final String text, final String searchString, final String replacement) { + return replace(text, searchString, replacement, -1); + } + + /** + *

              Replaces a String with another String inside a larger String, + * for the first {@code max} values of the search String.

              + * + *

              A {@code null} reference passed to this method is a no-op.

              + * + *
              +     * StringUtils.replace(null, *, *, *)         = null
              +     * StringUtils.replace("", *, *, *)           = ""
              +     * StringUtils.replace("any", null, *, *)     = "any"
              +     * StringUtils.replace("any", *, null, *)     = "any"
              +     * StringUtils.replace("any", "", *, *)       = "any"
              +     * StringUtils.replace("any", *, *, 0)        = "any"
              +     * StringUtils.replace("abaa", "a", null, -1) = "abaa"
              +     * StringUtils.replace("abaa", "a", "", -1)   = "b"
              +     * StringUtils.replace("abaa", "a", "z", 0)   = "abaa"
              +     * StringUtils.replace("abaa", "a", "z", 1)   = "zbaa"
              +     * StringUtils.replace("abaa", "a", "z", 2)   = "zbza"
              +     * StringUtils.replace("abaa", "a", "z", -1)  = "zbzz"
              +     * 
              + * + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null + * @param replacement the String to replace it with, may be null + * @param max maximum number of values to replace, or {@code -1} if no maximum + * @return the text with any replacements processed, + * {@code null} if null String input + */ + public static String replace(final String text, final String searchString, final String replacement, int max) { + if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) { + return text; + } + int start = 0; + int end = text.indexOf(searchString, start); + if (end == INDEX_NOT_FOUND) { + return text; + } + final int replLength = searchString.length(); + int increase = replacement.length() - replLength; + increase = increase < 0 ? 0 : increase; + increase *= max < 0 ? 16 : max > 64 ? 64 : max; + final StringBuilder buf = new StringBuilder(text.length() + increase); + while (end != INDEX_NOT_FOUND) { + buf.append(text.substring(start, end)).append(replacement); + start = end + replLength; + if (--max == 0) { + break; + } + end = text.indexOf(searchString, start); + } + buf.append(text.substring(start)); + return buf.toString(); + } + + /** + *

              + * Replaces all occurrences of Strings within another String. + *

              + * + *

              + * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. This will not repeat. For repeating replaces, call the + * overloaded method. + *

              + * + *
              +     *  StringUtils.replaceEach(null, *, *)        = null
              +     *  StringUtils.replaceEach("", *, *)          = ""
              +     *  StringUtils.replaceEach("aba", null, null) = "aba"
              +     *  StringUtils.replaceEach("aba", new String[0], null) = "aba"
              +     *  StringUtils.replaceEach("aba", null, new String[0]) = "aba"
              +     *  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"
              +     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"
              +     *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"
              +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
              +     *  (example of how it does not repeat)
              +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
              +     * 
              + * + * @param text + * text to search and replace in, no-op if null + * @param searchList + * the Strings to search for, no-op if null + * @param replacementList + * the Strings to replace them with, no-op if null + * @return the text with any replacements processed, {@code null} if + * null String input + * @throws IllegalArgumentException + * if the lengths of the arrays are not the same (null is ok, + * and/or size 0) + * @since 2.4 + */ + public static String replaceEach(final String text, final String[] searchList, final String[] replacementList) { + return replaceEach(text, searchList, replacementList, false, 0); + } + + /** + *

              + * Replaces all occurrences of Strings within another String. + *

              + * + *

              + * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. + *

              + * + *
              +     *  StringUtils.replaceEachRepeatedly(null, *, *) = null
              +     *  StringUtils.replaceEachRepeatedly("", *, *) = ""
              +     *  StringUtils.replaceEachRepeatedly("aba", null, null) = "aba"
              +     *  StringUtils.replaceEachRepeatedly("aba", new String[0], null) = "aba"
              +     *  StringUtils.replaceEachRepeatedly("aba", null, new String[0]) = "aba"
              +     *  StringUtils.replaceEachRepeatedly("aba", new String[]{"a"}, null) = "aba"
              +     *  StringUtils.replaceEachRepeatedly("aba", new String[]{"a"}, new String[]{""}) = "b"
              +     *  StringUtils.replaceEachRepeatedly("aba", new String[]{null}, new String[]{"a"}) = "aba"
              +     *  StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte"
              +     *  (example of how it repeats)
              +     *  StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "tcte"
              +     *  StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}) = IllegalStateException
              +     * 
              + * + * @param text + * text to search and replace in, no-op if null + * @param searchList + * the Strings to search for, no-op if null + * @param replacementList + * the Strings to replace them with, no-op if null + * @return the text with any replacements processed, {@code null} if + * null String input + * @throws IllegalStateException + * if the search is repeating and there is an endless loop due + * to outputs of one being inputs to another + * @throws IllegalArgumentException + * if the lengths of the arrays are not the same (null is ok, + * and/or size 0) + * @since 2.4 + */ + public static String replaceEachRepeatedly(final String text, final String[] searchList, final String[] replacementList) { + // timeToLive should be 0 if not used or nothing to replace, else it's + // the length of the replace array + final int timeToLive = searchList == null ? 0 : searchList.length; + return replaceEach(text, searchList, replacementList, true, timeToLive); + } + + /** + *

              + * Replace all occurrences of Strings within another String. + * This is a private recursive helper method for {@link #replaceEachRepeatedly(String, String[], String[])} and + * {@link #replaceEach(String, String[], String[])} + *

              + * + *

              + * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. + *

              + * + *
              +     *  StringUtils.replaceEach(null, *, *, *, *) = null
              +     *  StringUtils.replaceEach("", *, *, *, *) = ""
              +     *  StringUtils.replaceEach("aba", null, null, *, *) = "aba"
              +     *  StringUtils.replaceEach("aba", new String[0], null, *, *) = "aba"
              +     *  StringUtils.replaceEach("aba", null, new String[0], *, *) = "aba"
              +     *  StringUtils.replaceEach("aba", new String[]{"a"}, null, *, *) = "aba"
              +     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *, >=0) = "b"
              +     *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *, >=0) = "aba"
              +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *, >=0) = "wcte"
              +     *  (example of how it repeats)
              +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false, >=0) = "dcte"
              +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true, >=2) = "tcte"
              +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *, *) = IllegalStateException
              +     * 
              + * + * @param text + * text to search and replace in, no-op if null + * @param searchList + * the Strings to search for, no-op if null + * @param replacementList + * the Strings to replace them with, no-op if null + * @param repeat if true, then replace repeatedly + * until there are no more possible replacements or timeToLive < 0 + * @param timeToLive + * if less than 0 then there is a circular reference and endless + * loop + * @return the text with any replacements processed, {@code null} if + * null String input + * @throws IllegalStateException + * if the search is repeating and there is an endless loop due + * to outputs of one being inputs to another + * @throws IllegalArgumentException + * if the lengths of the arrays are not the same (null is ok, + * and/or size 0) + * @since 2.4 + */ + private static String replaceEach( + final String text, final String[] searchList, final String[] replacementList, final boolean repeat, final int timeToLive) { + + // mchyzer Performance note: This creates very few new objects (one major goal) + // let me know if there are performance requests, we can create a harness to measure + + if (text == null || text.isEmpty() || searchList == null || + searchList.length == 0 || replacementList == null || replacementList.length == 0) { + return text; + } + + // if recursing, this shouldn't be less than 0 + if (timeToLive < 0) { + throw new IllegalStateException("Aborting to protect against StackOverflowError - " + + "output of one loop is the input of another"); + } + + final int searchLength = searchList.length; + final int replacementLength = replacementList.length; + + // make sure lengths are ok, these need to be equal + if (searchLength != replacementLength) { + throw new IllegalArgumentException("Search and Replace array lengths don't match: " + + searchLength + + " vs " + + replacementLength); + } + + // keep track of which still have matches + final boolean[] noMoreMatchesForReplIndex = new boolean[searchLength]; + + // index on index that the match was found + int textIndex = -1; + int replaceIndex = -1; + int tempIndex = -1; + + // index of replace array that will replace the search string found + // NOTE: logic duplicated below START + for (int i = 0; i < searchLength; i++) { + if (noMoreMatchesForReplIndex[i] || searchList[i] == null || + searchList[i].isEmpty() || replacementList[i] == null) { + continue; + } + tempIndex = text.indexOf(searchList[i]); + + // see if we need to keep searching for this + if (tempIndex == -1) { + noMoreMatchesForReplIndex[i] = true; + } else { + if (textIndex == -1 || tempIndex < textIndex) { + textIndex = tempIndex; + replaceIndex = i; + } + } + } + // NOTE: logic mostly below END + + // no search strings found, we are done + if (textIndex == -1) { + return text; + } + + int start = 0; + + // get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit + int increase = 0; + + // count the replacement text elements that are larger than their corresponding text being replaced + for (int i = 0; i < searchList.length; i++) { + if (searchList[i] == null || replacementList[i] == null) { + continue; + } + final int greater = replacementList[i].length() - searchList[i].length(); + if (greater > 0) { + increase += 3 * greater; // assume 3 matches + } + } + // have upper-bound at 20% increase, then let Java take over + increase = Math.min(increase, text.length() / 5); + + final StringBuilder buf = new StringBuilder(text.length() + increase); + + while (textIndex != -1) { + + for (int i = start; i < textIndex; i++) { + buf.append(text.charAt(i)); + } + buf.append(replacementList[replaceIndex]); + + start = textIndex + searchList[replaceIndex].length(); + + textIndex = -1; + replaceIndex = -1; + tempIndex = -1; + // find the next earliest match + // NOTE: logic mostly duplicated above START + for (int i = 0; i < searchLength; i++) { + if (noMoreMatchesForReplIndex[i] || searchList[i] == null || + searchList[i].isEmpty() || replacementList[i] == null) { + continue; + } + tempIndex = text.indexOf(searchList[i], start); + + // see if we need to keep searching for this + if (tempIndex == -1) { + noMoreMatchesForReplIndex[i] = true; + } else { + if (textIndex == -1 || tempIndex < textIndex) { + textIndex = tempIndex; + replaceIndex = i; + } + } + } + // NOTE: logic duplicated above END + + } + final int textLength = text.length(); + for (int i = start; i < textLength; i++) { + buf.append(text.charAt(i)); + } + final String result = buf.toString(); + if (!repeat) { + return result; + } + + return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1); + } + + // Replace, character based + //----------------------------------------------------------------------- + /** + *

              Replaces all occurrences of a character in a String with another. + * This is a null-safe version of {@link String#replace(char, char)}.

              + * + *

              A {@code null} string input returns {@code null}. + * An empty ("") string input returns an empty string.

              + * + *
              +     * StringUtils.replaceChars(null, *, *)        = null
              +     * StringUtils.replaceChars("", *, *)          = ""
              +     * StringUtils.replaceChars("abcba", 'b', 'y') = "aycya"
              +     * StringUtils.replaceChars("abcba", 'z', 'y') = "abcba"
              +     * 
              + * + * @param str String to replace characters in, may be null + * @param searchChar the character to search for, may be null + * @param replaceChar the character to replace, may be null + * @return modified String, {@code null} if null string input + * @since 2.0 + */ + public static String replaceChars(final String str, final char searchChar, final char replaceChar) { + if (str == null) { + return null; + } + return str.replace(searchChar, replaceChar); + } + + /** + *

              Replaces multiple characters in a String in one go. + * This method can also be used to delete characters.

              + * + *

              For example:
              + * replaceChars("hello", "ho", "jy") = jelly.

              + * + *

              A {@code null} string input returns {@code null}. + * An empty ("") string input returns an empty string. + * A null or empty set of search characters returns the input string.

              + * + *

              The length of the search characters should normally equal the length + * of the replace characters. + * If the search characters is longer, then the extra search characters + * are deleted. + * If the search characters is shorter, then the extra replace characters + * are ignored.

              + * + *
              +     * StringUtils.replaceChars(null, *, *)           = null
              +     * StringUtils.replaceChars("", *, *)             = ""
              +     * StringUtils.replaceChars("abc", null, *)       = "abc"
              +     * StringUtils.replaceChars("abc", "", *)         = "abc"
              +     * StringUtils.replaceChars("abc", "b", null)     = "ac"
              +     * StringUtils.replaceChars("abc", "b", "")       = "ac"
              +     * StringUtils.replaceChars("abcba", "bc", "yz")  = "ayzya"
              +     * StringUtils.replaceChars("abcba", "bc", "y")   = "ayya"
              +     * StringUtils.replaceChars("abcba", "bc", "yzx") = "ayzya"
              +     * 
              + * + * @param str String to replace characters in, may be null + * @param searchChars a set of characters to search for, may be null + * @param replaceChars a set of characters to replace, may be null + * @return modified String, {@code null} if null string input + * @since 2.0 + */ + public static String replaceChars(final String str, final String searchChars, String replaceChars) { + if (isEmpty(str) || isEmpty(searchChars)) { + return str; + } + if (replaceChars == null) { + replaceChars = EMPTY; + } + boolean modified = false; + final int replaceCharsLength = replaceChars.length(); + final int strLength = str.length(); + final StringBuilder buf = new StringBuilder(strLength); + for (int i = 0; i < strLength; i++) { + final char ch = str.charAt(i); + final int index = searchChars.indexOf(ch); + if (index >= 0) { + modified = true; + if (index < replaceCharsLength) { + buf.append(replaceChars.charAt(index)); + } + } else { + buf.append(ch); + } + } + if (modified) { + return buf.toString(); + } + return str; + } + + // Overlay + //----------------------------------------------------------------------- + /** + *

              Overlays part of a String with another String.

              + * + *

              A {@code null} string input returns {@code null}. + * A negative index is treated as zero. + * An index greater than the string length is treated as the string length. + * The start index is always the smaller of the two indices.

              + * + *
              +     * StringUtils.overlay(null, *, *, *)            = null
              +     * StringUtils.overlay("", "abc", 0, 0)          = "abc"
              +     * StringUtils.overlay("abcdef", null, 2, 4)     = "abef"
              +     * StringUtils.overlay("abcdef", "", 2, 4)       = "abef"
              +     * StringUtils.overlay("abcdef", "", 4, 2)       = "abef"
              +     * StringUtils.overlay("abcdef", "zzzz", 2, 4)   = "abzzzzef"
              +     * StringUtils.overlay("abcdef", "zzzz", 4, 2)   = "abzzzzef"
              +     * StringUtils.overlay("abcdef", "zzzz", -1, 4)  = "zzzzef"
              +     * StringUtils.overlay("abcdef", "zzzz", 2, 8)   = "abzzzz"
              +     * StringUtils.overlay("abcdef", "zzzz", -2, -3) = "zzzzabcdef"
              +     * StringUtils.overlay("abcdef", "zzzz", 8, 10)  = "abcdefzzzz"
              +     * 
              + * + * @param str the String to do overlaying in, may be null + * @param overlay the String to overlay, may be null + * @param start the position to start overlaying at + * @param end the position to stop overlaying before + * @return overlayed String, {@code null} if null String input + * @since 2.0 + */ + public static String overlay(final String str, String overlay, int start, int end) { + if (str == null) { + return null; + } + if (overlay == null) { + overlay = EMPTY; + } + final int len = str.length(); + if (start < 0) { + start = 0; + } + if (start > len) { + start = len; + } + if (end < 0) { + end = 0; + } + if (end > len) { + end = len; + } + if (start > end) { + final int temp = start; + start = end; + end = temp; + } + return new StringBuilder(len + start - end + overlay.length() + 1) + .append(str.substring(0, start)) + .append(overlay) + .append(str.substring(end)) + .toString(); + } + + // Chomping + //----------------------------------------------------------------------- + /** + *

              Removes one newline from end of a String if it's there, + * otherwise leave it alone. A newline is "{@code \n}", + * "{@code \r}", or "{@code \r\n}".

              + * + *

              NOTE: This method changed in 2.0. + * It now more closely matches Perl chomp.

              + * + *
              +     * StringUtils.chomp(null)          = null
              +     * StringUtils.chomp("")            = ""
              +     * StringUtils.chomp("abc \r")      = "abc "
              +     * StringUtils.chomp("abc\n")       = "abc"
              +     * StringUtils.chomp("abc\r\n")     = "abc"
              +     * StringUtils.chomp("abc\r\n\r\n") = "abc\r\n"
              +     * StringUtils.chomp("abc\n\r")     = "abc\n"
              +     * StringUtils.chomp("abc\n\rabc")  = "abc\n\rabc"
              +     * StringUtils.chomp("\r")          = ""
              +     * StringUtils.chomp("\n")          = ""
              +     * StringUtils.chomp("\r\n")        = ""
              +     * 
              + * + * @param str the String to chomp a newline from, may be null + * @return String without newline, {@code null} if null String input + */ + public static String chomp(final String str) { + if (isEmpty(str)) { + return str; + } + + if (str.length() == 1) { + final char ch = str.charAt(0); + if (ch == CharUtils.CR || ch == CharUtils.LF) { + return EMPTY; + } + return str; + } + + int lastIdx = str.length() - 1; + final char last = str.charAt(lastIdx); + + if (last == CharUtils.LF) { + if (str.charAt(lastIdx - 1) == CharUtils.CR) { + lastIdx--; + } + } else if (last != CharUtils.CR) { + lastIdx++; + } + return str.substring(0, lastIdx); + } + + /** + *

              Removes {@code separator} from the end of + * {@code str} if it's there, otherwise leave it alone.

              + * + *

              NOTE: This method changed in version 2.0. + * It now more closely matches Perl chomp. + * For the previous behavior, use {@link #substringBeforeLast(String, String)}. + * This method uses {@link String#endsWith(String)}.

              + * + *
              +     * StringUtils.chomp(null, *)         = null
              +     * StringUtils.chomp("", *)           = ""
              +     * StringUtils.chomp("foobar", "bar") = "foo"
              +     * StringUtils.chomp("foobar", "baz") = "foobar"
              +     * StringUtils.chomp("foo", "foo")    = ""
              +     * StringUtils.chomp("foo ", "foo")   = "foo "
              +     * StringUtils.chomp(" foo", "foo")   = " "
              +     * StringUtils.chomp("foo", "foooo")  = "foo"
              +     * StringUtils.chomp("foo", "")       = "foo"
              +     * StringUtils.chomp("foo", null)     = "foo"
              +     * 
              + * + * @param str the String to chomp from, may be null + * @param separator separator String, may be null + * @return String without trailing separator, {@code null} if null String input + * @deprecated This feature will be removed in Lang 4.0, use {@link StringUtils#removeEnd(String, String)} instead + */ + @Deprecated + public static String chomp(final String str, final String separator) { + return removeEnd(str,separator); + } + + // Chopping + //----------------------------------------------------------------------- + /** + *

              Remove the last character from a String.

              + * + *

              If the String ends in {@code \r\n}, then remove both + * of them.

              + * + *
              +     * StringUtils.chop(null)          = null
              +     * StringUtils.chop("")            = ""
              +     * StringUtils.chop("abc \r")      = "abc "
              +     * StringUtils.chop("abc\n")       = "abc"
              +     * StringUtils.chop("abc\r\n")     = "abc"
              +     * StringUtils.chop("abc")         = "ab"
              +     * StringUtils.chop("abc\nabc")    = "abc\nab"
              +     * StringUtils.chop("a")           = ""
              +     * StringUtils.chop("\r")          = ""
              +     * StringUtils.chop("\n")          = ""
              +     * StringUtils.chop("\r\n")        = ""
              +     * 
              + * + * @param str the String to chop last character from, may be null + * @return String without last character, {@code null} if null String input + */ + public static String chop(final String str) { + if (str == null) { + return null; + } + final int strLen = str.length(); + if (strLen < 2) { + return EMPTY; + } + final int lastIdx = strLen - 1; + final String ret = str.substring(0, lastIdx); + final char last = str.charAt(lastIdx); + if (last == CharUtils.LF && ret.charAt(lastIdx - 1) == CharUtils.CR) { + return ret.substring(0, lastIdx - 1); + } + return ret; + } + + // Conversion + //----------------------------------------------------------------------- + + // Padding + //----------------------------------------------------------------------- + /** + *

              Repeat a String {@code repeat} times to form a + * new String.

              + * + *
              +     * StringUtils.repeat(null, 2) = null
              +     * StringUtils.repeat("", 0)   = ""
              +     * StringUtils.repeat("", 2)   = ""
              +     * StringUtils.repeat("a", 3)  = "aaa"
              +     * StringUtils.repeat("ab", 2) = "abab"
              +     * StringUtils.repeat("a", -2) = ""
              +     * 
              + * + * @param str the String to repeat, may be null + * @param repeat number of times to repeat str, negative treated as zero + * @return a new String consisting of the original String repeated, + * {@code null} if null String input + */ + public static String repeat(final String str, final int repeat) { + // Performance tuned for 2.0 (JDK1.4) + + if (str == null) { + return null; + } + if (repeat <= 0) { + return EMPTY; + } + final int inputLength = str.length(); + if (repeat == 1 || inputLength == 0) { + return str; + } + if (inputLength == 1 && repeat <= PAD_LIMIT) { + return repeat(str.charAt(0), repeat); + } + + final int outputLength = inputLength * repeat; + switch (inputLength) { + case 1 : + return repeat(str.charAt(0), repeat); + case 2 : + final char ch0 = str.charAt(0); + final char ch1 = str.charAt(1); + final char[] output2 = new char[outputLength]; + for (int i = repeat * 2 - 2; i >= 0; i--, i--) { + output2[i] = ch0; + output2[i + 1] = ch1; + } + return new String(output2); + default : + final StringBuilder buf = new StringBuilder(outputLength); + for (int i = 0; i < repeat; i++) { + buf.append(str); + } + return buf.toString(); + } + } + + /** + *

              Repeat a String {@code repeat} times to form a + * new String, with a String separator injected each time.

              + * + *
              +     * StringUtils.repeat(null, null, 2) = null
              +     * StringUtils.repeat(null, "x", 2)  = null
              +     * StringUtils.repeat("", null, 0)   = ""
              +     * StringUtils.repeat("", "", 2)     = ""
              +     * StringUtils.repeat("", "x", 3)    = "xxx"
              +     * StringUtils.repeat("?", ", ", 3)  = "?, ?, ?"
              +     * 
              + * + * @param str the String to repeat, may be null + * @param separator the String to inject, may be null + * @param repeat number of times to repeat str, negative treated as zero + * @return a new String consisting of the original String repeated, + * {@code null} if null String input + * @since 2.5 + */ + public static String repeat(final String str, final String separator, final int repeat) { + if(str == null || separator == null) { + return repeat(str, repeat); + } + // given that repeat(String, int) is quite optimized, better to rely on it than try and splice this into it + final String result = repeat(str + separator, repeat); + return removeEnd(result, separator); + } + + /** + *

              Returns padding using the specified delimiter repeated + * to a given length.

              + * + *
              +     * StringUtils.repeat('e', 0)  = ""
              +     * StringUtils.repeat('e', 3)  = "eee"
              +     * StringUtils.repeat('e', -2) = ""
              +     * 
              + * + *

              Note: this method doesn't not support padding with + * Unicode Supplementary Characters + * as they require a pair of {@code char}s to be represented. + * If you are needing to support full I18N of your applications + * consider using {@link #repeat(String, int)} instead. + *

              + * + * @param ch character to repeat + * @param repeat number of times to repeat char, negative treated as zero + * @return String with repeated character + * @see #repeat(String, int) + */ + public static String repeat(final char ch, final int repeat) { + final char[] buf = new char[repeat]; + for (int i = repeat - 1; i >= 0; i--) { + buf[i] = ch; + } + return new String(buf); + } + + /** + *

              Right pad a String with spaces (' ').

              + * + *

              The String is padded to the size of {@code size}.

              + * + *
              +     * StringUtils.rightPad(null, *)   = null
              +     * StringUtils.rightPad("", 3)     = "   "
              +     * StringUtils.rightPad("bat", 3)  = "bat"
              +     * StringUtils.rightPad("bat", 5)  = "bat  "
              +     * StringUtils.rightPad("bat", 1)  = "bat"
              +     * StringUtils.rightPad("bat", -1) = "bat"
              +     * 
              + * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @return right padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String rightPad(final String str, final int size) { + return rightPad(str, size, ' '); + } + + /** + *

              Right pad a String with a specified character.

              + * + *

              The String is padded to the size of {@code size}.

              + * + *
              +     * StringUtils.rightPad(null, *, *)     = null
              +     * StringUtils.rightPad("", 3, 'z')     = "zzz"
              +     * StringUtils.rightPad("bat", 3, 'z')  = "bat"
              +     * StringUtils.rightPad("bat", 5, 'z')  = "batzz"
              +     * StringUtils.rightPad("bat", 1, 'z')  = "bat"
              +     * StringUtils.rightPad("bat", -1, 'z') = "bat"
              +     * 
              + * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padChar the character to pad with + * @return right padded String or original String if no padding is necessary, + * {@code null} if null String input + * @since 2.0 + */ + public static String rightPad(final String str, final int size, final char padChar) { + if (str == null) { + return null; + } + final int pads = size - str.length(); + if (pads <= 0) { + return str; // returns original String when possible + } + if (pads > PAD_LIMIT) { + return rightPad(str, size, String.valueOf(padChar)); + } + return str.concat(repeat(padChar, pads)); + } + + /** + *

              Right pad a String with a specified String.

              + * + *

              The String is padded to the size of {@code size}.

              + * + *
              +     * StringUtils.rightPad(null, *, *)      = null
              +     * StringUtils.rightPad("", 3, "z")      = "zzz"
              +     * StringUtils.rightPad("bat", 3, "yz")  = "bat"
              +     * StringUtils.rightPad("bat", 5, "yz")  = "batyz"
              +     * StringUtils.rightPad("bat", 8, "yz")  = "batyzyzy"
              +     * StringUtils.rightPad("bat", 1, "yz")  = "bat"
              +     * StringUtils.rightPad("bat", -1, "yz") = "bat"
              +     * StringUtils.rightPad("bat", 5, null)  = "bat  "
              +     * StringUtils.rightPad("bat", 5, "")    = "bat  "
              +     * 
              + * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padStr the String to pad with, null or empty treated as single space + * @return right padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String rightPad(final String str, final int size, String padStr) { + if (str == null) { + return null; + } + if (isEmpty(padStr)) { + padStr = SPACE; + } + final int padLen = padStr.length(); + final int strLen = str.length(); + final int pads = size - strLen; + if (pads <= 0) { + return str; // returns original String when possible + } + if (padLen == 1 && pads <= PAD_LIMIT) { + return rightPad(str, size, padStr.charAt(0)); + } + + if (pads == padLen) { + return str.concat(padStr); + } else if (pads < padLen) { + return str.concat(padStr.substring(0, pads)); + } else { + final char[] padding = new char[pads]; + final char[] padChars = padStr.toCharArray(); + for (int i = 0; i < pads; i++) { + padding[i] = padChars[i % padLen]; + } + return str.concat(new String(padding)); + } + } + + /** + *

              Left pad a String with spaces (' ').

              + * + *

              The String is padded to the size of {@code size}.

              + * + *
              +     * StringUtils.leftPad(null, *)   = null
              +     * StringUtils.leftPad("", 3)     = "   "
              +     * StringUtils.leftPad("bat", 3)  = "bat"
              +     * StringUtils.leftPad("bat", 5)  = "  bat"
              +     * StringUtils.leftPad("bat", 1)  = "bat"
              +     * StringUtils.leftPad("bat", -1) = "bat"
              +     * 
              + * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @return left padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String leftPad(final String str, final int size) { + return leftPad(str, size, ' '); + } + + /** + *

              Left pad a String with a specified character.

              + * + *

              Pad to a size of {@code size}.

              + * + *
              +     * StringUtils.leftPad(null, *, *)     = null
              +     * StringUtils.leftPad("", 3, 'z')     = "zzz"
              +     * StringUtils.leftPad("bat", 3, 'z')  = "bat"
              +     * StringUtils.leftPad("bat", 5, 'z')  = "zzbat"
              +     * StringUtils.leftPad("bat", 1, 'z')  = "bat"
              +     * StringUtils.leftPad("bat", -1, 'z') = "bat"
              +     * 
              + * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padChar the character to pad with + * @return left padded String or original String if no padding is necessary, + * {@code null} if null String input + * @since 2.0 + */ + public static String leftPad(final String str, final int size, final char padChar) { + if (str == null) { + return null; + } + final int pads = size - str.length(); + if (pads <= 0) { + return str; // returns original String when possible + } + if (pads > PAD_LIMIT) { + return leftPad(str, size, String.valueOf(padChar)); + } + return repeat(padChar, pads).concat(str); + } + + /** + *

              Left pad a String with a specified String.

              + * + *

              Pad to a size of {@code size}.

              + * + *
              +     * StringUtils.leftPad(null, *, *)      = null
              +     * StringUtils.leftPad("", 3, "z")      = "zzz"
              +     * StringUtils.leftPad("bat", 3, "yz")  = "bat"
              +     * StringUtils.leftPad("bat", 5, "yz")  = "yzbat"
              +     * StringUtils.leftPad("bat", 8, "yz")  = "yzyzybat"
              +     * StringUtils.leftPad("bat", 1, "yz")  = "bat"
              +     * StringUtils.leftPad("bat", -1, "yz") = "bat"
              +     * StringUtils.leftPad("bat", 5, null)  = "  bat"
              +     * StringUtils.leftPad("bat", 5, "")    = "  bat"
              +     * 
              + * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padStr the String to pad with, null or empty treated as single space + * @return left padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String leftPad(final String str, final int size, String padStr) { + if (str == null) { + return null; + } + if (isEmpty(padStr)) { + padStr = SPACE; + } + final int padLen = padStr.length(); + final int strLen = str.length(); + final int pads = size - strLen; + if (pads <= 0) { + return str; // returns original String when possible + } + if (padLen == 1 && pads <= PAD_LIMIT) { + return leftPad(str, size, padStr.charAt(0)); + } + + if (pads == padLen) { + return padStr.concat(str); + } else if (pads < padLen) { + return padStr.substring(0, pads).concat(str); + } else { + final char[] padding = new char[pads]; + final char[] padChars = padStr.toCharArray(); + for (int i = 0; i < pads; i++) { + padding[i] = padChars[i % padLen]; + } + return new String(padding).concat(str); + } + } + + /** + * Gets a CharSequence length or {@code 0} if the CharSequence is + * {@code null}. + * + * @param cs + * a CharSequence or {@code null} + * @return CharSequence length or {@code 0} if the CharSequence is + * {@code null}. + * @since 2.4 + * @since 3.0 Changed signature from length(String) to length(CharSequence) + */ + public static int length(final CharSequence cs) { + return cs == null ? 0 : cs.length(); + } + + // Centering + //----------------------------------------------------------------------- + /** + *

              Centers a String in a larger String of size {@code size} + * using the space character (' ').

              + * + *

              If the size is less than the String length, the String is returned. + * A {@code null} String returns {@code null}. + * A negative size is treated as zero.

              + * + *

              Equivalent to {@code center(str, size, " ")}.

              + * + *
              +     * StringUtils.center(null, *)   = null
              +     * StringUtils.center("", 4)     = "    "
              +     * StringUtils.center("ab", -1)  = "ab"
              +     * StringUtils.center("ab", 4)   = " ab "
              +     * StringUtils.center("abcd", 2) = "abcd"
              +     * StringUtils.center("a", 4)    = " a  "
              +     * 
              + * + * @param str the String to center, may be null + * @param size the int size of new String, negative treated as zero + * @return centered String, {@code null} if null String input + */ + public static String center(final String str, final int size) { + return center(str, size, ' '); + } + + /** + *

              Centers a String in a larger String of size {@code size}. + * Uses a supplied character as the value to pad the String with.

              + * + *

              If the size is less than the String length, the String is returned. + * A {@code null} String returns {@code null}. + * A negative size is treated as zero.

              + * + *
              +     * StringUtils.center(null, *, *)     = null
              +     * StringUtils.center("", 4, ' ')     = "    "
              +     * StringUtils.center("ab", -1, ' ')  = "ab"
              +     * StringUtils.center("ab", 4, ' ')   = " ab "
              +     * StringUtils.center("abcd", 2, ' ') = "abcd"
              +     * StringUtils.center("a", 4, ' ')    = " a  "
              +     * StringUtils.center("a", 4, 'y')    = "yayy"
              +     * 
              + * + * @param str the String to center, may be null + * @param size the int size of new String, negative treated as zero + * @param padChar the character to pad the new String with + * @return centered String, {@code null} if null String input + * @since 2.0 + */ + public static String center(String str, final int size, final char padChar) { + if (str == null || size <= 0) { + return str; + } + final int strLen = str.length(); + final int pads = size - strLen; + if (pads <= 0) { + return str; + } + str = leftPad(str, strLen + pads / 2, padChar); + str = rightPad(str, size, padChar); + return str; + } + + /** + *

              Centers a String in a larger String of size {@code size}. + * Uses a supplied String as the value to pad the String with.

              + * + *

              If the size is less than the String length, the String is returned. + * A {@code null} String returns {@code null}. + * A negative size is treated as zero.

              + * + *
              +     * StringUtils.center(null, *, *)     = null
              +     * StringUtils.center("", 4, " ")     = "    "
              +     * StringUtils.center("ab", -1, " ")  = "ab"
              +     * StringUtils.center("ab", 4, " ")   = " ab "
              +     * StringUtils.center("abcd", 2, " ") = "abcd"
              +     * StringUtils.center("a", 4, " ")    = " a  "
              +     * StringUtils.center("a", 4, "yz")   = "yayz"
              +     * StringUtils.center("abc", 7, null) = "  abc  "
              +     * StringUtils.center("abc", 7, "")   = "  abc  "
              +     * 
              + * + * @param str the String to center, may be null + * @param size the int size of new String, negative treated as zero + * @param padStr the String to pad the new String with, must not be null or empty + * @return centered String, {@code null} if null String input + * @throws IllegalArgumentException if padStr is {@code null} or empty + */ + public static String center(String str, final int size, String padStr) { + if (str == null || size <= 0) { + return str; + } + if (isEmpty(padStr)) { + padStr = SPACE; + } + final int strLen = str.length(); + final int pads = size - strLen; + if (pads <= 0) { + return str; + } + str = leftPad(str, strLen + pads / 2, padStr); + str = rightPad(str, size, padStr); + return str; + } + + // Case conversion + //----------------------------------------------------------------------- + /** + *

              Converts a String to upper case as per {@link String#toUpperCase()}.

              + * + *

              A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.upperCase(null)  = null
              +     * StringUtils.upperCase("")    = ""
              +     * StringUtils.upperCase("aBc") = "ABC"
              +     * 
              + * + *

              Note: As described in the documentation for {@link String#toUpperCase()}, + * the result of this method is affected by the current locale. + * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)} + * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).

              + * + * @param str the String to upper case, may be null + * @return the upper cased String, {@code null} if null String input + */ + public static String upperCase(final String str) { + if (str == null) { + return null; + } + return str.toUpperCase(); + } + + /** + *

              Converts a String to upper case as per {@link String#toUpperCase(Locale)}.

              + * + *

              A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.upperCase(null, Locale.ENGLISH)  = null
              +     * StringUtils.upperCase("", Locale.ENGLISH)    = ""
              +     * StringUtils.upperCase("aBc", Locale.ENGLISH) = "ABC"
              +     * 
              + * + * @param str the String to upper case, may be null + * @param locale the locale that defines the case transformation rules, must not be null + * @return the upper cased String, {@code null} if null String input + * @since 2.5 + */ + public static String upperCase(final String str, final Locale locale) { + if (str == null) { + return null; + } + return str.toUpperCase(locale); + } + + /** + *

              Converts a String to lower case as per {@link String#toLowerCase()}.

              + * + *

              A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.lowerCase(null)  = null
              +     * StringUtils.lowerCase("")    = ""
              +     * StringUtils.lowerCase("aBc") = "abc"
              +     * 
              + * + *

              Note: As described in the documentation for {@link String#toLowerCase()}, + * the result of this method is affected by the current locale. + * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)} + * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).

              + * + * @param str the String to lower case, may be null + * @return the lower cased String, {@code null} if null String input + */ + public static String lowerCase(final String str) { + if (str == null) { + return null; + } + return str.toLowerCase(); + } + + /** + *

              Converts a String to lower case as per {@link String#toLowerCase(Locale)}.

              + * + *

              A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.lowerCase(null, Locale.ENGLISH)  = null
              +     * StringUtils.lowerCase("", Locale.ENGLISH)    = ""
              +     * StringUtils.lowerCase("aBc", Locale.ENGLISH) = "abc"
              +     * 
              + * + * @param str the String to lower case, may be null + * @param locale the locale that defines the case transformation rules, must not be null + * @return the lower cased String, {@code null} if null String input + * @since 2.5 + */ + public static String lowerCase(final String str, final Locale locale) { + if (str == null) { + return null; + } + return str.toLowerCase(locale); + } + + /** + *

              Capitalizes a String changing the first letter to title case as + * per {@link Character#toTitleCase(char)}. No other letters are changed.

              + * + *

              For a word based algorithm, see {@link WordUtils#capitalize(String)}. + * A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.capitalize(null)  = null
              +     * StringUtils.capitalize("")    = ""
              +     * StringUtils.capitalize("cat") = "Cat"
              +     * StringUtils.capitalize("cAt") = "CAt"
              +     * 
              + * + * @param str the String to capitalize, may be null + * @return the capitalized String, {@code null} if null String input + * @see WordUtils#capitalize(String) + * @see #uncapitalize(String) + * @since 2.0 + */ + public static String capitalize(final String str) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return str; + } + + final char firstChar = str.charAt(0); + if (Character.isTitleCase(firstChar)) { + // already capitalized + return str; + } + + return new StringBuilder(strLen) + .append(Character.toTitleCase(firstChar)) + .append(str.substring(1)) + .toString(); + } + + /** + *

              Uncapitalizes a String changing the first letter to title case as + * per {@link Character#toLowerCase(char)}. No other letters are changed.

              + * + *

              For a word based algorithm, see {@link WordUtils#uncapitalize(String)}. + * A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.uncapitalize(null)  = null
              +     * StringUtils.uncapitalize("")    = ""
              +     * StringUtils.uncapitalize("Cat") = "cat"
              +     * StringUtils.uncapitalize("CAT") = "cAT"
              +     * 
              + * + * @param str the String to uncapitalize, may be null + * @return the uncapitalized String, {@code null} if null String input + * @see WordUtils#uncapitalize(String) + * @see #capitalize(String) + * @since 2.0 + */ + public static String uncapitalize(final String str) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return str; + } + + final char firstChar = str.charAt(0); + if (Character.isLowerCase(firstChar)) { + // already uncapitalized + return str; + } + + return new StringBuilder(strLen) + .append(Character.toLowerCase(firstChar)) + .append(str.substring(1)) + .toString(); + } + + /** + *

              Swaps the case of a String changing upper and title case to + * lower case, and lower case to upper case.

              + * + *
                + *
              • Upper case character converts to Lower case
              • + *
              • Title case character converts to Lower case
              • + *
              • Lower case character converts to Upper case
              • + *
              + * + *

              For a word based algorithm, see {@link WordUtils#swapCase(String)}. + * A {@code null} input String returns {@code null}.

              + * + *
              +     * StringUtils.swapCase(null)                 = null
              +     * StringUtils.swapCase("")                   = ""
              +     * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
              +     * 
              + * + *

              NOTE: This method changed in Lang version 2.0. + * It no longer performs a word based algorithm. + * If you only use ASCII, you will notice no change. + * That functionality is available in org.apache.commons.lang3.text.WordUtils.

              + * + * @param str the String to swap case, may be null + * @return the changed String, {@code null} if null String input + */ + public static String swapCase(final String str) { + if (StringUtils.isEmpty(str)) { + return str; + } + + final char[] buffer = str.toCharArray(); + + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (Character.isUpperCase(ch)) { + buffer[i] = Character.toLowerCase(ch); + } else if (Character.isTitleCase(ch)) { + buffer[i] = Character.toLowerCase(ch); + } else if (Character.isLowerCase(ch)) { + buffer[i] = Character.toUpperCase(ch); + } + } + return new String(buffer); + } + + // Count matches + //----------------------------------------------------------------------- + /** + *

              Counts how many times the substring appears in the larger string.

              + * + *

              A {@code null} or empty ("") String input returns {@code 0}.

              + * + *
              +     * StringUtils.countMatches(null, *)       = 0
              +     * StringUtils.countMatches("", *)         = 0
              +     * StringUtils.countMatches("abba", null)  = 0
              +     * StringUtils.countMatches("abba", "")    = 0
              +     * StringUtils.countMatches("abba", "a")   = 2
              +     * StringUtils.countMatches("abba", "ab")  = 1
              +     * StringUtils.countMatches("abba", "xxx") = 0
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param sub the substring to count, may be null + * @return the number of occurrences, 0 if either CharSequence is {@code null} + * @since 3.0 Changed signature from countMatches(String, String) to countMatches(CharSequence, CharSequence) + */ + public static int countMatches(final CharSequence str, final CharSequence sub) { + if (isEmpty(str) || isEmpty(sub)) { + return 0; + } + int count = 0; + int idx = 0; + while ((idx = CharSequenceUtils.indexOf(str, sub, idx)) != INDEX_NOT_FOUND) { + count++; + idx += sub.length(); + } + return count; + } + + /** + *

              Counts how many times the char appears in the given string.

              + * + *

              A {@code null} or empty ("") String input returns {@code 0}.

              + * + *
              +     * StringUtils.countMatches(null, *)       = 0
              +     * StringUtils.countMatches("", *)         = 0
              +     * StringUtils.countMatches("abba", 0)  = 0
              +     * StringUtils.countMatches("abba", 'a')   = 2
              +     * StringUtils.countMatches("abba", 'b')  = 2
              +     * StringUtils.countMatches("abba", 'x') = 0
              +     * 
              + * + * @param str the CharSequence to check, may be null + * @param ch the char to count + * @return the number of occurrences, 0 if the CharSequence is {@code null} + * @since 3.4 + */ + public static int countMatches(final CharSequence str, final char ch) { + if (isEmpty(str)) { + return 0; + } + int count = 0; + // We could also call str.toCharArray() for faster look ups but that would generate more garbage. + for (int i = 0; i < str.length(); i++) { + if (ch == str.charAt(i)) { + count++; + } + } + return count; + } + + // Character Tests + //----------------------------------------------------------------------- + /** + *

              Checks if the CharSequence contains only Unicode letters.

              + * + *

              {@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

              + * + *
              +     * StringUtils.isAlpha(null)   = false
              +     * StringUtils.isAlpha("")     = false
              +     * StringUtils.isAlpha("  ")   = false
              +     * StringUtils.isAlpha("abc")  = true
              +     * StringUtils.isAlpha("ab2c") = false
              +     * StringUtils.isAlpha("ab-c") = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters, and is non-null + * @since 3.0 Changed signature from isAlpha(String) to isAlpha(CharSequence) + * @since 3.0 Changed "" to return false and not true + */ + public static boolean isAlpha(final CharSequence cs) { + if (isEmpty(cs)) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (Character.isLetter(cs.charAt(i)) == false) { + return false; + } + } + return true; + } + + /** + *

              Checks if the CharSequence contains only Unicode letters and + * space (' ').

              + * + *

              {@code null} will return {@code false} + * An empty CharSequence (length()=0) will return {@code true}.

              + * + *
              +     * StringUtils.isAlphaSpace(null)   = false
              +     * StringUtils.isAlphaSpace("")     = true
              +     * StringUtils.isAlphaSpace("  ")   = true
              +     * StringUtils.isAlphaSpace("abc")  = true
              +     * StringUtils.isAlphaSpace("ab c") = true
              +     * StringUtils.isAlphaSpace("ab2c") = false
              +     * StringUtils.isAlphaSpace("ab-c") = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters and space, + * and is non-null + * @since 3.0 Changed signature from isAlphaSpace(String) to isAlphaSpace(CharSequence) + */ + public static boolean isAlphaSpace(final CharSequence cs) { + if (cs == null) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (Character.isLetter(cs.charAt(i)) == false && cs.charAt(i) != ' ') { + return false; + } + } + return true; + } + + /** + *

              Checks if the CharSequence contains only Unicode letters or digits.

              + * + *

              {@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

              + * + *
              +     * StringUtils.isAlphanumeric(null)   = false
              +     * StringUtils.isAlphanumeric("")     = false
              +     * StringUtils.isAlphanumeric("  ")   = false
              +     * StringUtils.isAlphanumeric("abc")  = true
              +     * StringUtils.isAlphanumeric("ab c") = false
              +     * StringUtils.isAlphanumeric("ab2c") = true
              +     * StringUtils.isAlphanumeric("ab-c") = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters or digits, + * and is non-null + * @since 3.0 Changed signature from isAlphanumeric(String) to isAlphanumeric(CharSequence) + * @since 3.0 Changed "" to return false and not true + */ + public static boolean isAlphanumeric(final CharSequence cs) { + if (isEmpty(cs)) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (Character.isLetterOrDigit(cs.charAt(i)) == false) { + return false; + } + } + return true; + } + + /** + *

              Checks if the CharSequence contains only Unicode letters, digits + * or space ({@code ' '}).

              + * + *

              {@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

              + * + *
              +     * StringUtils.isAlphanumericSpace(null)   = false
              +     * StringUtils.isAlphanumericSpace("")     = true
              +     * StringUtils.isAlphanumericSpace("  ")   = true
              +     * StringUtils.isAlphanumericSpace("abc")  = true
              +     * StringUtils.isAlphanumericSpace("ab c") = true
              +     * StringUtils.isAlphanumericSpace("ab2c") = true
              +     * StringUtils.isAlphanumericSpace("ab-c") = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters, digits or space, + * and is non-null + * @since 3.0 Changed signature from isAlphanumericSpace(String) to isAlphanumericSpace(CharSequence) + */ + public static boolean isAlphanumericSpace(final CharSequence cs) { + if (cs == null) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (Character.isLetterOrDigit(cs.charAt(i)) == false && cs.charAt(i) != ' ') { + return false; + } + } + return true; + } + + /** + *

              Checks if the CharSequence contains only ASCII printable characters.

              + * + *

              {@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

              + * + *
              +     * StringUtils.isAsciiPrintable(null)     = false
              +     * StringUtils.isAsciiPrintable("")       = true
              +     * StringUtils.isAsciiPrintable(" ")      = true
              +     * StringUtils.isAsciiPrintable("Ceki")   = true
              +     * StringUtils.isAsciiPrintable("ab2c")   = true
              +     * StringUtils.isAsciiPrintable("!ab-c~") = true
              +     * StringUtils.isAsciiPrintable("\u0020") = true
              +     * StringUtils.isAsciiPrintable("\u0021") = true
              +     * StringUtils.isAsciiPrintable("\u007e") = true
              +     * StringUtils.isAsciiPrintable("\u007f") = false
              +     * StringUtils.isAsciiPrintable("Ceki G\u00fclc\u00fc") = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if every character is in the range + * 32 thru 126 + * @since 2.1 + * @since 3.0 Changed signature from isAsciiPrintable(String) to isAsciiPrintable(CharSequence) + */ + public static boolean isAsciiPrintable(final CharSequence cs) { + if (cs == null) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (CharUtils.isAsciiPrintable(cs.charAt(i)) == false) { + return false; + } + } + return true; + } + + /** + *

              Checks if the CharSequence contains only Unicode digits. + * A decimal point is not a Unicode digit and returns false.

              + * + *

              {@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

              + * + *

              Note that the method does not allow for a leading sign, either positive or negative. + * Also, if a String passes the numeric test, it may still generate a NumberFormatException + * when parsed by Integer.parseInt or Long.parseLong, e.g. if the value is outside the range + * for int or long respectively.

              + * + *
              +     * StringUtils.isNumeric(null)   = false
              +     * StringUtils.isNumeric("")     = false
              +     * StringUtils.isNumeric("  ")   = false
              +     * StringUtils.isNumeric("123")  = true
              +     * StringUtils.isNumeric("\u0967\u0968\u0969")  = true
              +     * StringUtils.isNumeric("12 3") = false
              +     * StringUtils.isNumeric("ab2c") = false
              +     * StringUtils.isNumeric("12-3") = false
              +     * StringUtils.isNumeric("12.3") = false
              +     * StringUtils.isNumeric("-123") = false
              +     * StringUtils.isNumeric("+123") = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains digits, and is non-null + * @since 3.0 Changed signature from isNumeric(String) to isNumeric(CharSequence) + * @since 3.0 Changed "" to return false and not true + */ + public static boolean isNumeric(final CharSequence cs) { + if (isEmpty(cs)) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (Character.isDigit(cs.charAt(i)) == false) { + return false; + } + } + return true; + } + + /** + *

              Checks if the CharSequence contains only Unicode digits or space + * ({@code ' '}). + * A decimal point is not a Unicode digit and returns false.

              + * + *

              {@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

              + * + *
              +     * StringUtils.isNumericSpace(null)   = false
              +     * StringUtils.isNumericSpace("")     = true
              +     * StringUtils.isNumericSpace("  ")   = true
              +     * StringUtils.isNumericSpace("123")  = true
              +     * StringUtils.isNumericSpace("12 3") = true
              +     * StringUtils.isNumeric("\u0967\u0968\u0969")  = true
              +     * StringUtils.isNumeric("\u0967\u0968 \u0969")  = true
              +     * StringUtils.isNumericSpace("ab2c") = false
              +     * StringUtils.isNumericSpace("12-3") = false
              +     * StringUtils.isNumericSpace("12.3") = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains digits or space, + * and is non-null + * @since 3.0 Changed signature from isNumericSpace(String) to isNumericSpace(CharSequence) + */ + public static boolean isNumericSpace(final CharSequence cs) { + if (cs == null) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (Character.isDigit(cs.charAt(i)) == false && cs.charAt(i) != ' ') { + return false; + } + } + return true; + } + + /** + *

              Checks if the CharSequence contains only whitespace.

              + * + *

              {@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

              + * + *
              +     * StringUtils.isWhitespace(null)   = false
              +     * StringUtils.isWhitespace("")     = true
              +     * StringUtils.isWhitespace("  ")   = true
              +     * StringUtils.isWhitespace("abc")  = false
              +     * StringUtils.isWhitespace("ab2c") = false
              +     * StringUtils.isWhitespace("ab-c") = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains whitespace, and is non-null + * @since 2.0 + * @since 3.0 Changed signature from isWhitespace(String) to isWhitespace(CharSequence) + */ + public static boolean isWhitespace(final CharSequence cs) { + if (cs == null) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (Character.isWhitespace(cs.charAt(i)) == false) { + return false; + } + } + return true; + } + + /** + *

              Checks if the CharSequence contains only lowercase characters.

              + * + *

              {@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

              + * + *
              +     * StringUtils.isAllLowerCase(null)   = false
              +     * StringUtils.isAllLowerCase("")     = false
              +     * StringUtils.isAllLowerCase("  ")   = false
              +     * StringUtils.isAllLowerCase("abc")  = true
              +     * StringUtils.isAllLowerCase("abC")  = false
              +     * StringUtils.isAllLowerCase("ab c") = false
              +     * StringUtils.isAllLowerCase("ab1c") = false
              +     * StringUtils.isAllLowerCase("ab/c") = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains lowercase characters, and is non-null + * @since 2.5 + * @since 3.0 Changed signature from isAllLowerCase(String) to isAllLowerCase(CharSequence) + */ + public static boolean isAllLowerCase(final CharSequence cs) { + if (cs == null || isEmpty(cs)) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (Character.isLowerCase(cs.charAt(i)) == false) { + return false; + } + } + return true; + } + + /** + *

              Checks if the CharSequence contains only uppercase characters.

              + * + *

              {@code null} will return {@code false}. + * An empty String (length()=0) will return {@code false}.

              + * + *
              +     * StringUtils.isAllUpperCase(null)   = false
              +     * StringUtils.isAllUpperCase("")     = false
              +     * StringUtils.isAllUpperCase("  ")   = false
              +     * StringUtils.isAllUpperCase("ABC")  = true
              +     * StringUtils.isAllUpperCase("aBC")  = false
              +     * StringUtils.isAllUpperCase("A C")  = false
              +     * StringUtils.isAllUpperCase("A1C")  = false
              +     * StringUtils.isAllUpperCase("A/C")  = false
              +     * 
              + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains uppercase characters, and is non-null + * @since 2.5 + * @since 3.0 Changed signature from isAllUpperCase(String) to isAllUpperCase(CharSequence) + */ + public static boolean isAllUpperCase(final CharSequence cs) { + if (cs == null || isEmpty(cs)) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (Character.isUpperCase(cs.charAt(i)) == false) { + return false; + } + } + return true; + } + + // Defaults + //----------------------------------------------------------------------- + /** + *

              Returns either the passed in String, + * or if the String is {@code null}, an empty String ("").

              + * + *
              +     * StringUtils.defaultString(null)  = ""
              +     * StringUtils.defaultString("")    = ""
              +     * StringUtils.defaultString("bat") = "bat"
              +     * 
              + * + * @see ObjectUtils#toString(Object) + * @see String#valueOf(Object) + * @param str the String to check, may be null + * @return the passed in String, or the empty String if it + * was {@code null} + */ + public static String defaultString(final String str) { + return str == null ? EMPTY : str; + } + + /** + *

              Returns either the passed in String, or if the String is + * {@code null}, the value of {@code defaultStr}.

              + * + *
              +     * StringUtils.defaultString(null, "NULL")  = "NULL"
              +     * StringUtils.defaultString("", "NULL")    = ""
              +     * StringUtils.defaultString("bat", "NULL") = "bat"
              +     * 
              + * + * @see ObjectUtils#toString(Object,String) + * @see String#valueOf(Object) + * @param str the String to check, may be null + * @param defaultStr the default String to return + * if the input is {@code null}, may be null + * @return the passed in String, or the default if it was {@code null} + */ + public static String defaultString(final String str, final String defaultStr) { + return str == null ? defaultStr : str; + } + + /** + *

              Returns either the passed in CharSequence, or if the CharSequence is + * whitespace, empty ("") or {@code null}, the value of {@code defaultStr}.

              + * + *
              +     * StringUtils.defaultIfBlank(null, "NULL")  = "NULL"
              +     * StringUtils.defaultIfBlank("", "NULL")    = "NULL"
              +     * StringUtils.defaultIfBlank(" ", "NULL")   = "NULL"
              +     * StringUtils.defaultIfBlank("bat", "NULL") = "bat"
              +     * StringUtils.defaultIfBlank("", null)      = null
              +     * 
              + * @param the specific kind of CharSequence + * @param str the CharSequence to check, may be null + * @param defaultStr the default CharSequence to return + * if the input is whitespace, empty ("") or {@code null}, may be null + * @return the passed in CharSequence, or the default + * @see StringUtils#defaultString(String, String) + */ + public static T defaultIfBlank(final T str, final T defaultStr) { + return isBlank(str) ? defaultStr : str; + } + + /** + *

              Returns either the passed in CharSequence, or if the CharSequence is + * empty or {@code null}, the value of {@code defaultStr}.

              + * + *
              +     * StringUtils.defaultIfEmpty(null, "NULL")  = "NULL"
              +     * StringUtils.defaultIfEmpty("", "NULL")    = "NULL"
              +     * StringUtils.defaultIfEmpty(" ", "NULL")   = " "
              +     * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
              +     * StringUtils.defaultIfEmpty("", null)      = null
              +     * 
              + * @param the specific kind of CharSequence + * @param str the CharSequence to check, may be null + * @param defaultStr the default CharSequence to return + * if the input is empty ("") or {@code null}, may be null + * @return the passed in CharSequence, or the default + * @see StringUtils#defaultString(String, String) + */ + public static T defaultIfEmpty(final T str, final T defaultStr) { + return isEmpty(str) ? defaultStr : str; + } + + // Reversing + //----------------------------------------------------------------------- + /** + *

              Reverses a String as per {@link StringBuilder#reverse()}.

              + * + *

              A {@code null} String returns {@code null}.

              + * + *
              +     * StringUtils.reverse(null)  = null
              +     * StringUtils.reverse("")    = ""
              +     * StringUtils.reverse("bat") = "tab"
              +     * 
              + * + * @param str the String to reverse, may be null + * @return the reversed String, {@code null} if null String input + */ + public static String reverse(final String str) { + if (str == null) { + return null; + } + return new StringBuilder(str).reverse().toString(); + } + + /** + *

              Reverses a String that is delimited by a specific character.

              + * + *

              The Strings between the delimiters are not reversed. + * Thus java.lang.String becomes String.lang.java (if the delimiter + * is {@code '.'}).

              + * + *
              +     * StringUtils.reverseDelimited(null, *)      = null
              +     * StringUtils.reverseDelimited("", *)        = ""
              +     * StringUtils.reverseDelimited("a.b.c", 'x') = "a.b.c"
              +     * StringUtils.reverseDelimited("a.b.c", ".") = "c.b.a"
              +     * 
              + * + * @param str the String to reverse, may be null + * @param separatorChar the separator character to use + * @return the reversed String, {@code null} if null String input + * @since 2.0 + */ + public static String reverseDelimited(final String str, final char separatorChar) { + if (str == null) { + return null; + } + // could implement manually, but simple way is to reuse other, + // probably slower, methods. + final String[] strs = split(str, separatorChar); + ArrayUtils.reverse(strs); + return join(strs, separatorChar); + } + + // Abbreviating + //----------------------------------------------------------------------- + /** + *

              Abbreviates a String using ellipses. This will turn + * "Now is the time for all good men" into "Now is the time for..."

              + * + *

              Specifically:

              + *
                + *
              • If the number of characters in {@code str} is less than or equal to + * {@code maxWidth}, return {@code str}.
              • + *
              • Else abbreviate it to {@code (substring(str, 0, max-3) + "...")}.
              • + *
              • If {@code maxWidth} is less than {@code 4}, throw an + * {@code IllegalArgumentException}.
              • + *
              • In no case will it return a String of length greater than + * {@code maxWidth}.
              • + *
              + * + *
              +     * StringUtils.abbreviate(null, *)      = null
              +     * StringUtils.abbreviate("", 4)        = ""
              +     * StringUtils.abbreviate("abcdefg", 6) = "abc..."
              +     * StringUtils.abbreviate("abcdefg", 7) = "abcdefg"
              +     * StringUtils.abbreviate("abcdefg", 8) = "abcdefg"
              +     * StringUtils.abbreviate("abcdefg", 4) = "a..."
              +     * StringUtils.abbreviate("abcdefg", 3) = IllegalArgumentException
              +     * 
              + * + * @param str the String to check, may be null + * @param maxWidth maximum length of result String, must be at least 4 + * @return abbreviated String, {@code null} if null String input + * @throws IllegalArgumentException if the width is too small + * @since 2.0 + */ + public static String abbreviate(final String str, final int maxWidth) { + return abbreviate(str, 0, maxWidth); + } + + /** + *

              Abbreviates a String using ellipses. This will turn + * "Now is the time for all good men" into "...is the time for..."

              + * + *

              Works like {@code abbreviate(String, int)}, but allows you to specify + * a "left edge" offset. Note that this left edge is not necessarily going to + * be the leftmost character in the result, or the first character following the + * ellipses, but it will appear somewhere in the result. + * + *

              In no case will it return a String of length greater than + * {@code maxWidth}.

              + * + *
              +     * StringUtils.abbreviate(null, *, *)                = null
              +     * StringUtils.abbreviate("", 0, 4)                  = ""
              +     * StringUtils.abbreviate("abcdefghijklmno", -1, 10) = "abcdefg..."
              +     * StringUtils.abbreviate("abcdefghijklmno", 0, 10)  = "abcdefg..."
              +     * StringUtils.abbreviate("abcdefghijklmno", 1, 10)  = "abcdefg..."
              +     * StringUtils.abbreviate("abcdefghijklmno", 4, 10)  = "abcdefg..."
              +     * StringUtils.abbreviate("abcdefghijklmno", 5, 10)  = "...fghi..."
              +     * StringUtils.abbreviate("abcdefghijklmno", 6, 10)  = "...ghij..."
              +     * StringUtils.abbreviate("abcdefghijklmno", 8, 10)  = "...ijklmno"
              +     * StringUtils.abbreviate("abcdefghijklmno", 10, 10) = "...ijklmno"
              +     * StringUtils.abbreviate("abcdefghijklmno", 12, 10) = "...ijklmno"
              +     * StringUtils.abbreviate("abcdefghij", 0, 3)        = IllegalArgumentException
              +     * StringUtils.abbreviate("abcdefghij", 5, 6)        = IllegalArgumentException
              +     * 
              + * + * @param str the String to check, may be null + * @param offset left edge of source String + * @param maxWidth maximum length of result String, must be at least 4 + * @return abbreviated String, {@code null} if null String input + * @throws IllegalArgumentException if the width is too small + * @since 2.0 + */ + public static String abbreviate(final String str, int offset, final int maxWidth) { + if (str == null) { + return null; + } + if (maxWidth < 4) { + throw new IllegalArgumentException("Minimum abbreviation width is 4"); + } + if (str.length() <= maxWidth) { + return str; + } + if (offset > str.length()) { + offset = str.length(); + } + if (str.length() - offset < maxWidth - 3) { + offset = str.length() - (maxWidth - 3); + } + final String abrevMarker = "..."; + if (offset <= 4) { + return str.substring(0, maxWidth - 3) + abrevMarker; + } + if (maxWidth < 7) { + throw new IllegalArgumentException("Minimum abbreviation width with offset is 7"); + } + if (offset + maxWidth - 3 < str.length()) { + return abrevMarker + abbreviate(str.substring(offset), maxWidth - 3); + } + return abrevMarker + str.substring(str.length() - (maxWidth - 3)); + } + + /** + *

              Abbreviates a String to the length passed, replacing the middle characters with the supplied + * replacement String.

              + * + *

              This abbreviation only occurs if the following criteria is met:

              + *
                + *
              • Neither the String for abbreviation nor the replacement String are null or empty
              • + *
              • The length to truncate to is less than the length of the supplied String
              • + *
              • The length to truncate to is greater than 0
              • + *
              • The abbreviated String will have enough room for the length supplied replacement String + * and the first and last characters of the supplied String for abbreviation
              • + *
              + *

              Otherwise, the returned String will be the same as the supplied String for abbreviation. + *

              + * + *
              +     * StringUtils.abbreviateMiddle(null, null, 0)      = null
              +     * StringUtils.abbreviateMiddle("abc", null, 0)      = "abc"
              +     * StringUtils.abbreviateMiddle("abc", ".", 0)      = "abc"
              +     * StringUtils.abbreviateMiddle("abc", ".", 3)      = "abc"
              +     * StringUtils.abbreviateMiddle("abcdef", ".", 4)     = "ab.f"
              +     * 
              + * + * @param str the String to abbreviate, may be null + * @param middle the String to replace the middle characters with, may be null + * @param length the length to abbreviate {@code str} to. + * @return the abbreviated String if the above criteria is met, or the original String supplied for abbreviation. + * @since 2.5 + */ + public static String abbreviateMiddle(final String str, final String middle, final int length) { + if (isEmpty(str) || isEmpty(middle)) { + return str; + } + + if (length >= str.length() || length < middle.length()+2) { + return str; + } + + final int targetSting = length-middle.length(); + final int startOffset = targetSting/2+targetSting%2; + final int endOffset = str.length()-targetSting/2; + + final StringBuilder builder = new StringBuilder(length); + builder.append(str.substring(0,startOffset)); + builder.append(middle); + builder.append(str.substring(endOffset)); + + return builder.toString(); + } + + // Difference + //----------------------------------------------------------------------- + /** + *

              Compares two Strings, and returns the portion where they differ. + * More precisely, return the remainder of the second String, + * starting from where it's different from the first. This means that + * the difference between "abc" and "ab" is the empty String and not "c".

              + * + *

              For example, + * {@code difference("i am a machine", "i am a robot") -> "robot"}.

              + * + *
              +     * StringUtils.difference(null, null) = null
              +     * StringUtils.difference("", "") = ""
              +     * StringUtils.difference("", "abc") = "abc"
              +     * StringUtils.difference("abc", "") = ""
              +     * StringUtils.difference("abc", "abc") = ""
              +     * StringUtils.difference("abc", "ab") = ""
              +     * StringUtils.difference("ab", "abxyz") = "xyz"
              +     * StringUtils.difference("abcde", "abxyz") = "xyz"
              +     * StringUtils.difference("abcde", "xyz") = "xyz"
              +     * 
              + * + * @param str1 the first String, may be null + * @param str2 the second String, may be null + * @return the portion of str2 where it differs from str1; returns the + * empty String if they are equal + * @see #indexOfDifference(CharSequence,CharSequence) + * @since 2.0 + */ + public static String difference(final String str1, final String str2) { + if (str1 == null) { + return str2; + } + if (str2 == null) { + return str1; + } + final int at = indexOfDifference(str1, str2); + if (at == INDEX_NOT_FOUND) { + return EMPTY; + } + return str2.substring(at); + } + + /** + *

              Compares two CharSequences, and returns the index at which the + * CharSequences begin to differ.

              + * + *

              For example, + * {@code indexOfDifference("i am a machine", "i am a robot") -> 7}

              + * + *
              +     * StringUtils.indexOfDifference(null, null) = -1
              +     * StringUtils.indexOfDifference("", "") = -1
              +     * StringUtils.indexOfDifference("", "abc") = 0
              +     * StringUtils.indexOfDifference("abc", "") = 0
              +     * StringUtils.indexOfDifference("abc", "abc") = -1
              +     * StringUtils.indexOfDifference("ab", "abxyz") = 2
              +     * StringUtils.indexOfDifference("abcde", "abxyz") = 2
              +     * StringUtils.indexOfDifference("abcde", "xyz") = 0
              +     * 
              + * + * @param cs1 the first CharSequence, may be null + * @param cs2 the second CharSequence, may be null + * @return the index where cs1 and cs2 begin to differ; -1 if they are equal + * @since 2.0 + * @since 3.0 Changed signature from indexOfDifference(String, String) to + * indexOfDifference(CharSequence, CharSequence) + */ + public static int indexOfDifference(final CharSequence cs1, final CharSequence cs2) { + if (cs1 == cs2) { + return INDEX_NOT_FOUND; + } + if (cs1 == null || cs2 == null) { + return 0; + } + int i; + for (i = 0; i < cs1.length() && i < cs2.length(); ++i) { + if (cs1.charAt(i) != cs2.charAt(i)) { + break; + } + } + if (i < cs2.length() || i < cs1.length()) { + return i; + } + return INDEX_NOT_FOUND; + } + + /** + *

              Compares all CharSequences in an array and returns the index at which the + * CharSequences begin to differ.

              + * + *

              For example, + * indexOfDifference(new String[] {"i am a machine", "i am a robot"}) -> 7

              + * + *
              +     * StringUtils.indexOfDifference(null) = -1
              +     * StringUtils.indexOfDifference(new String[] {}) = -1
              +     * StringUtils.indexOfDifference(new String[] {"abc"}) = -1
              +     * StringUtils.indexOfDifference(new String[] {null, null}) = -1
              +     * StringUtils.indexOfDifference(new String[] {"", ""}) = -1
              +     * StringUtils.indexOfDifference(new String[] {"", null}) = 0
              +     * StringUtils.indexOfDifference(new String[] {"abc", null, null}) = 0
              +     * StringUtils.indexOfDifference(new String[] {null, null, "abc"}) = 0
              +     * StringUtils.indexOfDifference(new String[] {"", "abc"}) = 0
              +     * StringUtils.indexOfDifference(new String[] {"abc", ""}) = 0
              +     * StringUtils.indexOfDifference(new String[] {"abc", "abc"}) = -1
              +     * StringUtils.indexOfDifference(new String[] {"abc", "a"}) = 1
              +     * StringUtils.indexOfDifference(new String[] {"ab", "abxyz"}) = 2
              +     * StringUtils.indexOfDifference(new String[] {"abcde", "abxyz"}) = 2
              +     * StringUtils.indexOfDifference(new String[] {"abcde", "xyz"}) = 0
              +     * StringUtils.indexOfDifference(new String[] {"xyz", "abcde"}) = 0
              +     * StringUtils.indexOfDifference(new String[] {"i am a machine", "i am a robot"}) = 7
              +     * 
              + * + * @param css array of CharSequences, entries may be null + * @return the index where the strings begin to differ; -1 if they are all equal + * @since 2.4 + * @since 3.0 Changed signature from indexOfDifference(String...) to indexOfDifference(CharSequence...) + */ + public static int indexOfDifference(final CharSequence... css) { + if (css == null || css.length <= 1) { + return INDEX_NOT_FOUND; + } + boolean anyStringNull = false; + boolean allStringsNull = true; + final int arrayLen = css.length; + int shortestStrLen = Integer.MAX_VALUE; + int longestStrLen = 0; + + // find the min and max string lengths; this avoids checking to make + // sure we are not exceeding the length of the string each time through + // the bottom loop. + for (int i = 0; i < arrayLen; i++) { + if (css[i] == null) { + anyStringNull = true; + shortestStrLen = 0; + } else { + allStringsNull = false; + shortestStrLen = Math.min(css[i].length(), shortestStrLen); + longestStrLen = Math.max(css[i].length(), longestStrLen); + } + } + + // handle lists containing all nulls or all empty strings + if (allStringsNull || longestStrLen == 0 && !anyStringNull) { + return INDEX_NOT_FOUND; + } + + // handle lists containing some nulls or some empty strings + if (shortestStrLen == 0) { + return 0; + } + + // find the position with the first difference across all strings + int firstDiff = -1; + for (int stringPos = 0; stringPos < shortestStrLen; stringPos++) { + final char comparisonChar = css[0].charAt(stringPos); + for (int arrayPos = 1; arrayPos < arrayLen; arrayPos++) { + if (css[arrayPos].charAt(stringPos) != comparisonChar) { + firstDiff = stringPos; + break; + } + } + if (firstDiff != -1) { + break; + } + } + + if (firstDiff == -1 && shortestStrLen != longestStrLen) { + // we compared all of the characters up to the length of the + // shortest string and didn't find a match, but the string lengths + // vary, so return the length of the shortest string. + return shortestStrLen; + } + return firstDiff; + } + + /** + *

              Compares all Strings in an array and returns the initial sequence of + * characters that is common to all of them.

              + * + *

              For example, + * getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) -> "i am a "

              + * + *
              +     * StringUtils.getCommonPrefix(null) = ""
              +     * StringUtils.getCommonPrefix(new String[] {}) = ""
              +     * StringUtils.getCommonPrefix(new String[] {"abc"}) = "abc"
              +     * StringUtils.getCommonPrefix(new String[] {null, null}) = ""
              +     * StringUtils.getCommonPrefix(new String[] {"", ""}) = ""
              +     * StringUtils.getCommonPrefix(new String[] {"", null}) = ""
              +     * StringUtils.getCommonPrefix(new String[] {"abc", null, null}) = ""
              +     * StringUtils.getCommonPrefix(new String[] {null, null, "abc"}) = ""
              +     * StringUtils.getCommonPrefix(new String[] {"", "abc"}) = ""
              +     * StringUtils.getCommonPrefix(new String[] {"abc", ""}) = ""
              +     * StringUtils.getCommonPrefix(new String[] {"abc", "abc"}) = "abc"
              +     * StringUtils.getCommonPrefix(new String[] {"abc", "a"}) = "a"
              +     * StringUtils.getCommonPrefix(new String[] {"ab", "abxyz"}) = "ab"
              +     * StringUtils.getCommonPrefix(new String[] {"abcde", "abxyz"}) = "ab"
              +     * StringUtils.getCommonPrefix(new String[] {"abcde", "xyz"}) = ""
              +     * StringUtils.getCommonPrefix(new String[] {"xyz", "abcde"}) = ""
              +     * StringUtils.getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) = "i am a "
              +     * 
              + * + * @param strs array of String objects, entries may be null + * @return the initial sequence of characters that are common to all Strings + * in the array; empty String if the array is null, the elements are all null + * or if there is no common prefix. + * @since 2.4 + */ + public static String getCommonPrefix(final String... strs) { + if (strs == null || strs.length == 0) { + return EMPTY; + } + final int smallestIndexOfDiff = indexOfDifference(strs); + if (smallestIndexOfDiff == INDEX_NOT_FOUND) { + // all strings were identical + if (strs[0] == null) { + return EMPTY; + } + return strs[0]; + } else if (smallestIndexOfDiff == 0) { + // there were no common initial characters + return EMPTY; + } else { + // we found a common initial character sequence + return strs[0].substring(0, smallestIndexOfDiff); + } + } + + // Misc + //----------------------------------------------------------------------- + /** + *

              Find the Levenshtein distance between two Strings.

              + * + *

              This is the number of changes needed to change one String into + * another, where each change is a single character modification (deletion, + * insertion or substitution).

              + * + *

              The previous implementation of the Levenshtein distance algorithm + * was from http://www.merriampark.com/ld.htm

              + * + *

              Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError + * which can occur when my Java implementation is used with very large strings.
              + * This implementation of the Levenshtein distance algorithm + * is from http://www.merriampark.com/ldjava.htm

              + * + *
              +     * StringUtils.getLevenshteinDistance(null, *)             = IllegalArgumentException
              +     * StringUtils.getLevenshteinDistance(*, null)             = IllegalArgumentException
              +     * StringUtils.getLevenshteinDistance("","")               = 0
              +     * StringUtils.getLevenshteinDistance("","a")              = 1
              +     * StringUtils.getLevenshteinDistance("aaapppp", "")       = 7
              +     * StringUtils.getLevenshteinDistance("frog", "fog")       = 1
              +     * StringUtils.getLevenshteinDistance("fly", "ant")        = 3
              +     * StringUtils.getLevenshteinDistance("elephant", "hippo") = 7
              +     * StringUtils.getLevenshteinDistance("hippo", "elephant") = 7
              +     * StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
              +     * StringUtils.getLevenshteinDistance("hello", "hallo")    = 1
              +     * 
              + * + * @param s the first String, must not be null + * @param t the second String, must not be null + * @return result distance + * @throws IllegalArgumentException if either String input {@code null} + * @since 3.0 Changed signature from getLevenshteinDistance(String, String) to + * getLevenshteinDistance(CharSequence, CharSequence) + */ + public static int getLevenshteinDistance(CharSequence s, CharSequence t) { + if (s == null || t == null) { + throw new IllegalArgumentException("Strings must not be null"); + } + + /* + The difference between this impl. and the previous is that, rather + than creating and retaining a matrix of size s.length() + 1 by t.length() + 1, + we maintain two single-dimensional arrays of length s.length() + 1. The first, d, + is the 'current working' distance array that maintains the newest distance cost + counts as we iterate through the characters of String s. Each time we increment + the index of String t we are comparing, d is copied to p, the second int[]. Doing so + allows us to retain the previous cost counts as required by the algorithm (taking + the minimum of the cost count to the left, up one, and diagonally up and to the left + of the current cost count being calculated). (Note that the arrays aren't really + copied anymore, just switched...this is clearly much better than cloning an array + or doing a System.arraycopy() each time through the outer loop.) + + Effectively, the difference between the two implementations is this one does not + cause an out of memory condition when calculating the LD over two very large strings. + */ + + int n = s.length(); // length of s + int m = t.length(); // length of t + + if (n == 0) { + return m; + } else if (m == 0) { + return n; + } + + if (n > m) { + // swap the input strings to consume less memory + final CharSequence tmp = s; + s = t; + t = tmp; + n = m; + m = t.length(); + } + + int p[] = new int[n + 1]; //'previous' cost array, horizontally + int d[] = new int[n + 1]; // cost array, horizontally + int _d[]; //placeholder to assist in swapping p and d + + // indexes into strings s and t + int i; // iterates through s + int j; // iterates through t + + char t_j; // jth character of t + + int cost; // cost + + for (i = 0; i <= n; i++) { + p[i] = i; + } + + for (j = 1; j <= m; j++) { + t_j = t.charAt(j - 1); + d[0] = j; + + for (i = 1; i <= n; i++) { + cost = s.charAt(i - 1) == t_j ? 0 : 1; + // minimum of cell to the left+1, to the top+1, diagonally left and up +cost + d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost); + } + + // copy current distance counts to 'previous row' distance counts + _d = p; + p = d; + d = _d; + } + + // our last action in the above loop was to switch d and p, so p now + // actually has the most recent cost counts + return p[n]; + } + + /** + *

              Find the Levenshtein distance between two Strings if it's less than or equal to a given + * threshold.

              + * + *

              This is the number of changes needed to change one String into + * another, where each change is a single character modification (deletion, + * insertion or substitution).

              + * + *

              This implementation follows from Algorithms on Strings, Trees and Sequences by Dan Gusfield + * and Chas Emerick's implementation of the Levenshtein distance algorithm from + * http://www.merriampark.com/ld.htm

              + * + *
              +     * StringUtils.getLevenshteinDistance(null, *, *)             = IllegalArgumentException
              +     * StringUtils.getLevenshteinDistance(*, null, *)             = IllegalArgumentException
              +     * StringUtils.getLevenshteinDistance(*, *, -1)               = IllegalArgumentException
              +     * StringUtils.getLevenshteinDistance("","", 0)               = 0
              +     * StringUtils.getLevenshteinDistance("aaapppp", "", 8)       = 7
              +     * StringUtils.getLevenshteinDistance("aaapppp", "", 7)       = 7
              +     * StringUtils.getLevenshteinDistance("aaapppp", "", 6))      = -1
              +     * StringUtils.getLevenshteinDistance("elephant", "hippo", 7) = 7
              +     * StringUtils.getLevenshteinDistance("elephant", "hippo", 6) = -1
              +     * StringUtils.getLevenshteinDistance("hippo", "elephant", 7) = 7
              +     * StringUtils.getLevenshteinDistance("hippo", "elephant", 6) = -1
              +     * 
              + * + * @param s the first String, must not be null + * @param t the second String, must not be null + * @param threshold the target threshold, must not be negative + * @return result distance, or {@code -1} if the distance would be greater than the threshold + * @throws IllegalArgumentException if either String input {@code null} or negative threshold + */ + public static int getLevenshteinDistance(CharSequence s, CharSequence t, final int threshold) { + if (s == null || t == null) { + throw new IllegalArgumentException("Strings must not be null"); + } + if (threshold < 0) { + throw new IllegalArgumentException("Threshold must not be negative"); + } + + /* + This implementation only computes the distance if it's less than or equal to the + threshold value, returning -1 if it's greater. The advantage is performance: unbounded + distance is O(nm), but a bound of k allows us to reduce it to O(km) time by only + computing a diagonal stripe of width 2k + 1 of the cost table. + It is also possible to use this to compute the unbounded Levenshtein distance by starting + the threshold at 1 and doubling each time until the distance is found; this is O(dm), where + d is the distance. + + One subtlety comes from needing to ignore entries on the border of our stripe + eg. + p[] = |#|#|#|* + d[] = *|#|#|#| + We must ignore the entry to the left of the leftmost member + We must ignore the entry above the rightmost member + + Another subtlety comes from our stripe running off the matrix if the strings aren't + of the same size. Since string s is always swapped to be the shorter of the two, + the stripe will always run off to the upper right instead of the lower left of the matrix. + + As a concrete example, suppose s is of length 5, t is of length 7, and our threshold is 1. + In this case we're going to walk a stripe of length 3. The matrix would look like so: + + 1 2 3 4 5 + 1 |#|#| | | | + 2 |#|#|#| | | + 3 | |#|#|#| | + 4 | | |#|#|#| + 5 | | | |#|#| + 6 | | | | |#| + 7 | | | | | | + + Note how the stripe leads off the table as there is no possible way to turn a string of length 5 + into one of length 7 in edit distance of 1. + + Additionally, this implementation decreases memory usage by using two + single-dimensional arrays and swapping them back and forth instead of allocating + an entire n by m matrix. This requires a few minor changes, such as immediately returning + when it's detected that the stripe has run off the matrix and initially filling the arrays with + large values so that entries we don't compute are ignored. + + See Algorithms on Strings, Trees and Sequences by Dan Gusfield for some discussion. + */ + + int n = s.length(); // length of s + int m = t.length(); // length of t + + // if one string is empty, the edit distance is necessarily the length of the other + if (n == 0) { + return m <= threshold ? m : -1; + } else if (m == 0) { + return n <= threshold ? n : -1; + } + + if (n > m) { + // swap the two strings to consume less memory + final CharSequence tmp = s; + s = t; + t = tmp; + n = m; + m = t.length(); + } + + int p[] = new int[n + 1]; // 'previous' cost array, horizontally + int d[] = new int[n + 1]; // cost array, horizontally + int _d[]; // placeholder to assist in swapping p and d + + // fill in starting table values + final int boundary = Math.min(n, threshold) + 1; + for (int i = 0; i < boundary; i++) { + p[i] = i; + } + // these fills ensure that the value above the rightmost entry of our + // stripe will be ignored in following loop iterations + Arrays.fill(p, boundary, p.length, Integer.MAX_VALUE); + Arrays.fill(d, Integer.MAX_VALUE); + + // iterates through t + for (int j = 1; j <= m; j++) { + final char t_j = t.charAt(j - 1); // jth character of t + d[0] = j; + + // compute stripe indices, constrain to array size + final int min = Math.max(1, j - threshold); + final int max = (j > Integer.MAX_VALUE - threshold) ? n : Math.min(n, j + threshold); + + // the stripe may lead off of the table if s and t are of different sizes + if (min > max) { + return -1; + } + + // ignore entry left of leftmost + if (min > 1) { + d[min - 1] = Integer.MAX_VALUE; + } + + // iterates through [min, max] in s + for (int i = min; i <= max; i++) { + if (s.charAt(i - 1) == t_j) { + // diagonally left and up + d[i] = p[i - 1]; + } else { + // 1 + minimum of cell to the left, to the top, diagonally left and up + d[i] = 1 + Math.min(Math.min(d[i - 1], p[i]), p[i - 1]); + } + } + + // copy current distance counts to 'previous row' distance counts + _d = p; + p = d; + d = _d; + } + + // if p[n] is greater than the threshold, there's no guarantee on it being the correct + // distance + if (p[n] <= threshold) { + return p[n]; + } + return -1; + } + + /** + *

              Find the Jaro Winkler Distance which indicates the similarity score between two Strings.

              + * + *

              The Jaro measure is the weighted sum of percentage of matched characters from each file and transposed characters. + * Winkler increased this measure for matching initial characters.

              + * + *

              This implementation is based on the Jaro Winkler similarity algorithm + * from http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance.

              + * + *
              +     * StringUtils.getJaroWinklerDistance(null, null)          = IllegalArgumentException
              +     * StringUtils.getJaroWinklerDistance("","")               = 0.0
              +     * StringUtils.getJaroWinklerDistance("","a")              = 0.0
              +     * StringUtils.getJaroWinklerDistance("aaapppp", "")       = 0.0
              +     * StringUtils.getJaroWinklerDistance("frog", "fog")       = 0.93
              +     * StringUtils.getJaroWinklerDistance("fly", "ant")        = 0.0
              +     * StringUtils.getJaroWinklerDistance("elephant", "hippo") = 0.44
              +     * StringUtils.getJaroWinklerDistance("hippo", "elephant") = 0.44
              +     * StringUtils.getJaroWinklerDistance("hippo", "zzzzzzzz") = 0.0
              +     * StringUtils.getJaroWinklerDistance("hello", "hallo")    = 0.88
              +     * StringUtils.getJaroWinklerDistance("ABC Corporation", "ABC Corp") = 0.91
              +     * StringUtils.getJaroWinklerDistance("D N H Enterprises Inc", "D & H Enterprises, Inc.") = 0.93
              +     * StringUtils.getJaroWinklerDistance("My Gym Children's Fitness Center", "My Gym. Childrens Fitness") = 0.94
              +     * StringUtils.getJaroWinklerDistance("PENNSYLVANIA", "PENNCISYLVNIA")    = 0.9
              +     * 
              + * + * @param first the first String, must not be null + * @param second the second String, must not be null + * @return result distance + * @throws IllegalArgumentException if either String input {@code null} + * @since 3.3 + */ + public static double getJaroWinklerDistance(final CharSequence first, final CharSequence second) { + final double DEFAULT_SCALING_FACTOR = 0.1; + + if (first == null || second == null) { + throw new IllegalArgumentException("Strings must not be null"); + } + + final double jaro = score(first,second); + final int cl = commonPrefixLength(first, second); + final double matchScore = Math.round((jaro + (DEFAULT_SCALING_FACTOR * cl * (1.0 - jaro))) *100.0)/100.0; + + return matchScore; + } + + /** + * This method returns the Jaro-Winkler score for string matching. + * @param first the first string to be matched + * @param second the second string to be machted + * @return matching score without scaling factor impact + */ + private static double score(final CharSequence first, final CharSequence second) { + String shorter; + String longer; + + // Determine which String is longer. + if (first.length() > second.length()) { + longer = first.toString().toLowerCase(); + shorter = second.toString().toLowerCase(); + } else { + longer = second.toString().toLowerCase(); + shorter = first.toString().toLowerCase(); + } + + // Calculate the half length() distance of the shorter String. + final int halflength = (shorter.length() / 2) + 1; + + // Find the set of matching characters between the shorter and longer strings. Note that + // the set of matching characters may be different depending on the order of the strings. + final String m1 = getSetOfMatchingCharacterWithin(shorter, longer, halflength); + final String m2 = getSetOfMatchingCharacterWithin(longer, shorter, halflength); + + // If one or both of the sets of common characters is empty, then + // there is no similarity between the two strings. + if (m1.length() == 0 || m2.length() == 0) { + return 0.0; + } + + // If the set of common characters is not the same size, then + // there is no similarity between the two strings, either. + if (m1.length() != m2.length()) { + return 0.0; + } + + // Calculate the number of transposition between the two sets + // of common characters. + final int transpositions = transpositions(m1, m2); + + // Calculate the distance. + final double dist = + (m1.length() / ((double)shorter.length()) + + m2.length() / ((double)longer.length()) + + (m1.length() - transpositions) / ((double)m1.length())) / 3.0; + return dist; + } + + /** + *

              Find the Fuzzy Distance which indicates the similarity score between two Strings.

              + * + *

              This string matching algorithm is similar to the algorithms of editors such as Sublime Text, + * TextMate, Atom and others. One point is given for every matched character. Subsequent + * matches yield two bonus points. A higher score indicates a higher similarity.

              + * + *
              +     * StringUtils.getFuzzyDistance(null, null, null)                                    = IllegalArgumentException
              +     * StringUtils.getFuzzyDistance("", "", Locale.ENGLISH)                              = 0
              +     * StringUtils.getFuzzyDistance("Workshop", "b", Locale.ENGLISH)                     = 0
              +     * StringUtils.getFuzzyDistance("Room", "o", Locale.ENGLISH)                         = 1
              +     * StringUtils.getFuzzyDistance("Workshop", "w", Locale.ENGLISH)                     = 1
              +     * StringUtils.getFuzzyDistance("Workshop", "ws", Locale.ENGLISH)                    = 2
              +     * StringUtils.getFuzzyDistance("Workshop", "wo", Locale.ENGLISH)                    = 4
              +     * StringUtils.getFuzzyDistance("Apache Software Foundation", "asf", Locale.ENGLISH) = 3
              +     * 
              + * + * @param term a full term that should be matched against, must not be null + * @param query the query that will be matched against a term, must not be null + * @param locale This string matching logic is case insensitive. A locale is necessary to normalize + * both Strings to lower case. + * @return result score + * @throws IllegalArgumentException if either String input {@code null} or Locale input {@code null} + * @since 3.4 + */ + public static int getFuzzyDistance(final CharSequence term, final CharSequence query, final Locale locale) { + if (term == null || query == null) { + throw new IllegalArgumentException("Strings must not be null"); + } else if (locale == null) { + throw new IllegalArgumentException("Locale must not be null"); + } + + // fuzzy logic is case insensitive. We normalize the Strings to lower + // case right from the start. Turning characters to lower case + // via Character.toLowerCase(char) is unfortunately insufficient + // as it does not accept a locale. + final String termLowerCase = term.toString().toLowerCase(locale); + final String queryLowerCase = query.toString().toLowerCase(locale); + + // the resulting score + int score = 0; + + // the position in the term which will be scanned next for potential + // query character matches + int termIndex = 0; + + // index of the previously matched character in the term + int previousMatchingCharacterIndex = Integer.MIN_VALUE; + + for (int queryIndex = 0; queryIndex < queryLowerCase.length(); queryIndex++) { + final char queryChar = queryLowerCase.charAt(queryIndex); + + boolean termCharacterMatchFound = false; + for (; termIndex < termLowerCase.length() && !termCharacterMatchFound; termIndex++) { + final char termChar = termLowerCase.charAt(termIndex); + + if (queryChar == termChar) { + // simple character matches result in one point + score++; + + // subsequent character matches further improve + // the score. + if (previousMatchingCharacterIndex + 1 == termIndex) { + score += 2; + } + + previousMatchingCharacterIndex = termIndex; + + // we can leave the nested loop. Every character in the + // query can match at most one character in the term. + termCharacterMatchFound = true; + } + } + } + + return score; + } + + /** + * Gets a set of matching characters between two strings. + * + *

              + * + * @param first The first string. + * @param second The second string. + * @param limit The maximum distance to consider. + * @return A string contain the set of common characters. + */ + private static String getSetOfMatchingCharacterWithin(final CharSequence first, final CharSequence second, final int limit) { + final StringBuilder common = new StringBuilder(); + final StringBuilder copy = new StringBuilder(second); + + for (int i = 0; i < first.length(); i++) { + final char ch = first.charAt(i); + boolean found = false; + + // See if the character is within the limit positions away from the original position of that character. + for (int j = Math.max(0, i - limit); !found && j < Math.min(i + limit, second.length()); j++) { + if (copy.charAt(j) == ch) { + found = true; + common.append(ch); + copy.setCharAt(j,'*'); + } + } + } + return common.toString(); + } + + /** + * Calculates the number of transposition between two strings. + * @param first The first string. + * @param second The second string. + * @return The number of transposition between the two strings. + */ + private static int transpositions(final CharSequence first, final CharSequence second) { + int transpositions = 0; + for (int i = 0; i < first.length(); i++) { + if (first.charAt(i) != second.charAt(i)) { + transpositions++; + } + } + return transpositions / 2; + } + + /** + * Calculates the number of characters from the beginning of the strings that match exactly one-to-one, + * up to a maximum of four (4) characters. + * @param first The first string. + * @param second The second string. + * @return A number between 0 and 4. + */ + private static int commonPrefixLength(final CharSequence first, final CharSequence second) { + final int result = getCommonPrefix(first.toString(), second.toString()).length(); + + // Limit the result to 4. + return result > 4 ? 4 : result; + } + + // startsWith + //----------------------------------------------------------------------- + + /** + *

              Check if a CharSequence starts with a specified prefix.

              + * + *

              {@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

              + * + *
              +     * StringUtils.startsWith(null, null)      = true
              +     * StringUtils.startsWith(null, "abc")     = false
              +     * StringUtils.startsWith("abcdef", null)  = false
              +     * StringUtils.startsWith("abcdef", "abc") = true
              +     * StringUtils.startsWith("ABCDEF", "abc") = false
              +     * 
              + * + * @see java.lang.String#startsWith(String) + * @param str the CharSequence to check, may be null + * @param prefix the prefix to find, may be null + * @return {@code true} if the CharSequence starts with the prefix, case sensitive, or + * both {@code null} + * @since 2.4 + * @since 3.0 Changed signature from startsWith(String, String) to startsWith(CharSequence, CharSequence) + */ + public static boolean startsWith(final CharSequence str, final CharSequence prefix) { + return startsWith(str, prefix, false); + } + + /** + *

              Case insensitive check if a CharSequence starts with a specified prefix.

              + * + *

              {@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case insensitive.

              + * + *
              +     * StringUtils.startsWithIgnoreCase(null, null)      = true
              +     * StringUtils.startsWithIgnoreCase(null, "abc")     = false
              +     * StringUtils.startsWithIgnoreCase("abcdef", null)  = false
              +     * StringUtils.startsWithIgnoreCase("abcdef", "abc") = true
              +     * StringUtils.startsWithIgnoreCase("ABCDEF", "abc") = true
              +     * 
              + * + * @see java.lang.String#startsWith(String) + * @param str the CharSequence to check, may be null + * @param prefix the prefix to find, may be null + * @return {@code true} if the CharSequence starts with the prefix, case insensitive, or + * both {@code null} + * @since 2.4 + * @since 3.0 Changed signature from startsWithIgnoreCase(String, String) to startsWithIgnoreCase(CharSequence, CharSequence) + */ + public static boolean startsWithIgnoreCase(final CharSequence str, final CharSequence prefix) { + return startsWith(str, prefix, true); + } + + /** + *

              Check if a CharSequence starts with a specified prefix (optionally case insensitive).

              + * + * @see java.lang.String#startsWith(String) + * @param str the CharSequence to check, may be null + * @param prefix the prefix to find, may be null + * @param ignoreCase indicates whether the compare should ignore case + * (case insensitive) or not. + * @return {@code true} if the CharSequence starts with the prefix or + * both {@code null} + */ + private static boolean startsWith(final CharSequence str, final CharSequence prefix, final boolean ignoreCase) { + if (str == null || prefix == null) { + return str == null && prefix == null; + } + if (prefix.length() > str.length()) { + return false; + } + return CharSequenceUtils.regionMatches(str, ignoreCase, 0, prefix, 0, prefix.length()); + } + + /** + *

              Check if a CharSequence starts with any of an array of specified strings.

              + * + *
              +     * StringUtils.startsWithAny(null, null)      = false
              +     * StringUtils.startsWithAny(null, new String[] {"abc"})  = false
              +     * StringUtils.startsWithAny("abcxyz", null)     = false
              +     * StringUtils.startsWithAny("abcxyz", new String[] {""}) = false
              +     * StringUtils.startsWithAny("abcxyz", new String[] {"abc"}) = true
              +     * StringUtils.startsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
              +     * 
              + * + * @param string the CharSequence to check, may be null + * @param searchStrings the CharSequences to find, may be null or empty + * @return {@code true} if the CharSequence starts with any of the the prefixes, case insensitive, or + * both {@code null} + * @since 2.5 + * @since 3.0 Changed signature from startsWithAny(String, String[]) to startsWithAny(CharSequence, CharSequence...) + */ + public static boolean startsWithAny(final CharSequence string, final CharSequence... searchStrings) { + if (isEmpty(string) || ArrayUtils.isEmpty(searchStrings)) { + return false; + } + for (final CharSequence searchString : searchStrings) { + if (startsWith(string, searchString)) { + return true; + } + } + return false; + } + + // endsWith + //----------------------------------------------------------------------- + + /** + *

              Check if a CharSequence ends with a specified suffix.

              + * + *

              {@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

              + * + *
              +     * StringUtils.endsWith(null, null)      = true
              +     * StringUtils.endsWith(null, "def")     = false
              +     * StringUtils.endsWith("abcdef", null)  = false
              +     * StringUtils.endsWith("abcdef", "def") = true
              +     * StringUtils.endsWith("ABCDEF", "def") = false
              +     * StringUtils.endsWith("ABCDEF", "cde") = false
              +     * 
              + * + * @see java.lang.String#endsWith(String) + * @param str the CharSequence to check, may be null + * @param suffix the suffix to find, may be null + * @return {@code true} if the CharSequence ends with the suffix, case sensitive, or + * both {@code null} + * @since 2.4 + * @since 3.0 Changed signature from endsWith(String, String) to endsWith(CharSequence, CharSequence) + */ + public static boolean endsWith(final CharSequence str, final CharSequence suffix) { + return endsWith(str, suffix, false); + } + + /** + *

              Case insensitive check if a CharSequence ends with a specified suffix.

              + * + *

              {@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case insensitive.

              + * + *
              +     * StringUtils.endsWithIgnoreCase(null, null)      = true
              +     * StringUtils.endsWithIgnoreCase(null, "def")     = false
              +     * StringUtils.endsWithIgnoreCase("abcdef", null)  = false
              +     * StringUtils.endsWithIgnoreCase("abcdef", "def") = true
              +     * StringUtils.endsWithIgnoreCase("ABCDEF", "def") = true
              +     * StringUtils.endsWithIgnoreCase("ABCDEF", "cde") = false
              +     * 
              + * + * @see java.lang.String#endsWith(String) + * @param str the CharSequence to check, may be null + * @param suffix the suffix to find, may be null + * @return {@code true} if the CharSequence ends with the suffix, case insensitive, or + * both {@code null} + * @since 2.4 + * @since 3.0 Changed signature from endsWithIgnoreCase(String, String) to endsWithIgnoreCase(CharSequence, CharSequence) + */ + public static boolean endsWithIgnoreCase(final CharSequence str, final CharSequence suffix) { + return endsWith(str, suffix, true); + } + + /** + *

              Check if a CharSequence ends with a specified suffix (optionally case insensitive).

              + * + * @see java.lang.String#endsWith(String) + * @param str the CharSequence to check, may be null + * @param suffix the suffix to find, may be null + * @param ignoreCase indicates whether the compare should ignore case + * (case insensitive) or not. + * @return {@code true} if the CharSequence starts with the prefix or + * both {@code null} + */ + private static boolean endsWith(final CharSequence str, final CharSequence suffix, final boolean ignoreCase) { + if (str == null || suffix == null) { + return str == null && suffix == null; + } + if (suffix.length() > str.length()) { + return false; + } + final int strOffset = str.length() - suffix.length(); + return CharSequenceUtils.regionMatches(str, ignoreCase, strOffset, suffix, 0, suffix.length()); + } + + /** + *

              + * Similar to http://www.w3.org/TR/xpath/#function-normalize + * -space + *

              + *

              + * The function returns the argument string with whitespace normalized by using + * {@link #trim(String)} to remove leading and trailing whitespace + * and then replacing sequences of whitespace characters by a single space. + *

              + * In XML Whitespace characters are the same as those allowed by the S production, which is S ::= (#x20 | #x9 | #xD | #xA)+ + *

              + * Java's regexp pattern \s defines whitespace as [ \t\n\x0B\f\r] + * + *

              For reference:

              + *
                + *
              • \x0B = vertical tab
              • + *
              • \f = #xC = form feed
              • + *
              • #x20 = space
              • + *
              • #x9 = \t
              • + *
              • #xA = \n
              • + *
              • #xD = \r
              • + *
              + * + *

              + * The difference is that Java's whitespace includes vertical tab and form feed, which this functional will also + * normalize. Additionally {@link #trim(String)} removes control characters (char <= 32) from both + * ends of this String. + *

              + * + * @see Pattern + * @see #trim(String) + * @see http://www.w3.org/TR/xpath/#function-normalize-space + * @param str the source String to normalize whitespaces from, may be null + * @return the modified string with whitespace normalized, {@code null} if null String input + * + * @since 3.0 + */ + public static String normalizeSpace(final String str) { + // LANG-1020: Improved performance significantly by normalizing manually instead of using regex + // See https://github.com/librucha/commons-lang-normalizespaces-benchmark for performance test + if (isEmpty(str)) { + return str; + } + final int size = str.length(); + final char[] newChars = new char[size]; + int count = 0; + int whitespacesCount = 0; + boolean startWhitespaces = true; + for (int i = 0; i < size; i++) { + char actualChar = str.charAt(i); + boolean isWhitespace = Character.isWhitespace(actualChar); + if (!isWhitespace) { + startWhitespaces = false; + newChars[count++] = (actualChar == 160 ? 32 : actualChar); + whitespacesCount = 0; + } else { + if (whitespacesCount == 0 && !startWhitespaces) { + newChars[count++] = SPACE.charAt(0); + } + whitespacesCount++; + } + } + if (startWhitespaces) { + return EMPTY; + } + return new String(newChars, 0, count - (whitespacesCount > 0 ? 1 : 0)); + } + + /** + *

              Check if a CharSequence ends with any of an array of specified strings.

              + * + *
              +     * StringUtils.endsWithAny(null, null)      = false
              +     * StringUtils.endsWithAny(null, new String[] {"abc"})  = false
              +     * StringUtils.endsWithAny("abcxyz", null)     = false
              +     * StringUtils.endsWithAny("abcxyz", new String[] {""}) = true
              +     * StringUtils.endsWithAny("abcxyz", new String[] {"xyz"}) = true
              +     * StringUtils.endsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
              +     * 
              + * + * @param string the CharSequence to check, may be null + * @param searchStrings the CharSequences to find, may be null or empty + * @return {@code true} if the CharSequence ends with any of the the prefixes, case insensitive, or + * both {@code null} + * @since 3.0 + */ + public static boolean endsWithAny(final CharSequence string, final CharSequence... searchStrings) { + if (isEmpty(string) || ArrayUtils.isEmpty(searchStrings)) { + return false; + } + for (final CharSequence searchString : searchStrings) { + if (endsWith(string, searchString)) { + return true; + } + } + return false; + } + + /** + * Appends the suffix to the end of the string if the string does not + * already end in the suffix. + * + * @param str The string. + * @param suffix The suffix to append to the end of the string. + * @param ignoreCase Indicates whether the compare should ignore case. + * @param suffixes Additional suffixes that are valid terminators (optional). + * + * @return A new String if suffix was appened, the same string otherwise. + */ + private static String appendIfMissing(final String str, final CharSequence suffix, final boolean ignoreCase, final CharSequence... suffixes) { + if (str == null || isEmpty(suffix) || endsWith(str, suffix, ignoreCase)) { + return str; + } + if (suffixes != null && suffixes.length > 0) { + for (final CharSequence s : suffixes) { + if (endsWith(str, s, ignoreCase)) { + return str; + } + } + } + return str + suffix.toString(); + } + + /** + * Appends the suffix to the end of the string if the string does not + * already end with any the suffixes. + * + *
              +     * StringUtils.appendIfMissing(null, null) = null
              +     * StringUtils.appendIfMissing("abc", null) = "abc"
              +     * StringUtils.appendIfMissing("", "xyz") = "xyz"
              +     * StringUtils.appendIfMissing("abc", "xyz") = "abcxyz"
              +     * StringUtils.appendIfMissing("abcxyz", "xyz") = "abcxyz"
              +     * StringUtils.appendIfMissing("abcXYZ", "xyz") = "abcXYZxyz"
              +     * 
              + *

              With additional suffixes,

              + *
              +     * StringUtils.appendIfMissing(null, null, null) = null
              +     * StringUtils.appendIfMissing("abc", null, null) = "abc"
              +     * StringUtils.appendIfMissing("", "xyz", null) = "xyz"
              +     * StringUtils.appendIfMissing("abc", "xyz", new CharSequence[]{null}) = "abcxyz"
              +     * StringUtils.appendIfMissing("abc", "xyz", "") = "abc"
              +     * StringUtils.appendIfMissing("abc", "xyz", "mno") = "abcxyz"
              +     * StringUtils.appendIfMissing("abcxyz", "xyz", "mno") = "abcxyz"
              +     * StringUtils.appendIfMissing("abcmno", "xyz", "mno") = "abcmno"
              +     * StringUtils.appendIfMissing("abcXYZ", "xyz", "mno") = "abcXYZxyz"
              +     * StringUtils.appendIfMissing("abcMNO", "xyz", "mno") = "abcMNOxyz"
              +     * 
              + * + * @param str The string. + * @param suffix The suffix to append to the end of the string. + * @param suffixes Additional suffixes that are valid terminators. + * + * @return A new String if suffix was appened, the same string otherwise. + * + * @since 3.2 + */ + public static String appendIfMissing(final String str, final CharSequence suffix, final CharSequence... suffixes) { + return appendIfMissing(str, suffix, false, suffixes); + } + + /** + * Appends the suffix to the end of the string if the string does not + * already end, case insensitive, with any of the suffixes. + * + *
              +     * StringUtils.appendIfMissingIgnoreCase(null, null) = null
              +     * StringUtils.appendIfMissingIgnoreCase("abc", null) = "abc"
              +     * StringUtils.appendIfMissingIgnoreCase("", "xyz") = "xyz"
              +     * StringUtils.appendIfMissingIgnoreCase("abc", "xyz") = "abcxyz"
              +     * StringUtils.appendIfMissingIgnoreCase("abcxyz", "xyz") = "abcxyz"
              +     * StringUtils.appendIfMissingIgnoreCase("abcXYZ", "xyz") = "abcXYZ"
              +     * 
              + *

              With additional suffixes,

              + *
              +     * StringUtils.appendIfMissingIgnoreCase(null, null, null) = null
              +     * StringUtils.appendIfMissingIgnoreCase("abc", null, null) = "abc"
              +     * StringUtils.appendIfMissingIgnoreCase("", "xyz", null) = "xyz"
              +     * StringUtils.appendIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "abcxyz"
              +     * StringUtils.appendIfMissingIgnoreCase("abc", "xyz", "") = "abc"
              +     * StringUtils.appendIfMissingIgnoreCase("abc", "xyz", "mno") = "axyz"
              +     * StringUtils.appendIfMissingIgnoreCase("abcxyz", "xyz", "mno") = "abcxyz"
              +     * StringUtils.appendIfMissingIgnoreCase("abcmno", "xyz", "mno") = "abcmno"
              +     * StringUtils.appendIfMissingIgnoreCase("abcXYZ", "xyz", "mno") = "abcXYZ"
              +     * StringUtils.appendIfMissingIgnoreCase("abcMNO", "xyz", "mno") = "abcMNO"
              +     * 
              + * + * @param str The string. + * @param suffix The suffix to append to the end of the string. + * @param suffixes Additional suffixes that are valid terminators. + * + * @return A new String if suffix was appened, the same string otherwise. + * + * @since 3.2 + */ + public static String appendIfMissingIgnoreCase(final String str, final CharSequence suffix, final CharSequence... suffixes) { + return appendIfMissing(str, suffix, true, suffixes); + } + + /** + * Prepends the prefix to the start of the string if the string does not + * already start with any of the prefixes. + * + * @param str The string. + * @param prefix The prefix to prepend to the start of the string. + * @param ignoreCase Indicates whether the compare should ignore case. + * @param prefixes Additional prefixes that are valid (optional). + * + * @return A new String if prefix was prepended, the same string otherwise. + */ + private static String prependIfMissing(final String str, final CharSequence prefix, final boolean ignoreCase, final CharSequence... prefixes) { + if (str == null || isEmpty(prefix) || startsWith(str, prefix, ignoreCase)) { + return str; + } + if (prefixes != null && prefixes.length > 0) { + for (final CharSequence p : prefixes) { + if (startsWith(str, p, ignoreCase)) { + return str; + } + } + } + return prefix.toString() + str; + } + + /** + * Prepends the prefix to the start of the string if the string does not + * already start with any of the prefixes. + * + *
              +     * StringUtils.prependIfMissing(null, null) = null
              +     * StringUtils.prependIfMissing("abc", null) = "abc"
              +     * StringUtils.prependIfMissing("", "xyz") = "xyz"
              +     * StringUtils.prependIfMissing("abc", "xyz") = "xyzabc"
              +     * StringUtils.prependIfMissing("xyzabc", "xyz") = "xyzabc"
              +     * StringUtils.prependIfMissing("XYZabc", "xyz") = "xyzXYZabc"
              +     * 
              + *

              With additional prefixes,

              + *
              +     * StringUtils.prependIfMissing(null, null, null) = null
              +     * StringUtils.prependIfMissing("abc", null, null) = "abc"
              +     * StringUtils.prependIfMissing("", "xyz", null) = "xyz"
              +     * StringUtils.prependIfMissing("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
              +     * StringUtils.prependIfMissing("abc", "xyz", "") = "abc"
              +     * StringUtils.prependIfMissing("abc", "xyz", "mno") = "xyzabc"
              +     * StringUtils.prependIfMissing("xyzabc", "xyz", "mno") = "xyzabc"
              +     * StringUtils.prependIfMissing("mnoabc", "xyz", "mno") = "mnoabc"
              +     * StringUtils.prependIfMissing("XYZabc", "xyz", "mno") = "xyzXYZabc"
              +     * StringUtils.prependIfMissing("MNOabc", "xyz", "mno") = "xyzMNOabc"
              +     * 
              + * + * @param str The string. + * @param prefix The prefix to prepend to the start of the string. + * @param prefixes Additional prefixes that are valid. + * + * @return A new String if prefix was prepended, the same string otherwise. + * + * @since 3.2 + */ + public static String prependIfMissing(final String str, final CharSequence prefix, final CharSequence... prefixes) { + return prependIfMissing(str, prefix, false, prefixes); + } + + /** + * Prepends the prefix to the start of the string if the string does not + * already start, case insensitive, with any of the prefixes. + * + *
              +     * StringUtils.prependIfMissingIgnoreCase(null, null) = null
              +     * StringUtils.prependIfMissingIgnoreCase("abc", null) = "abc"
              +     * StringUtils.prependIfMissingIgnoreCase("", "xyz") = "xyz"
              +     * StringUtils.prependIfMissingIgnoreCase("abc", "xyz") = "xyzabc"
              +     * StringUtils.prependIfMissingIgnoreCase("xyzabc", "xyz") = "xyzabc"
              +     * StringUtils.prependIfMissingIgnoreCase("XYZabc", "xyz") = "XYZabc"
              +     * 
              + *

              With additional prefixes,

              + *
              +     * StringUtils.prependIfMissingIgnoreCase(null, null, null) = null
              +     * StringUtils.prependIfMissingIgnoreCase("abc", null, null) = "abc"
              +     * StringUtils.prependIfMissingIgnoreCase("", "xyz", null) = "xyz"
              +     * StringUtils.prependIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
              +     * StringUtils.prependIfMissingIgnoreCase("abc", "xyz", "") = "abc"
              +     * StringUtils.prependIfMissingIgnoreCase("abc", "xyz", "mno") = "xyzabc"
              +     * StringUtils.prependIfMissingIgnoreCase("xyzabc", "xyz", "mno") = "xyzabc"
              +     * StringUtils.prependIfMissingIgnoreCase("mnoabc", "xyz", "mno") = "mnoabc"
              +     * StringUtils.prependIfMissingIgnoreCase("XYZabc", "xyz", "mno") = "XYZabc"
              +     * StringUtils.prependIfMissingIgnoreCase("MNOabc", "xyz", "mno") = "MNOabc"
              +     * 
              + * + * @param str The string. + * @param prefix The prefix to prepend to the start of the string. + * @param prefixes Additional prefixes that are valid (optional). + * + * @return A new String if prefix was prepended, the same string otherwise. + * + * @since 3.2 + */ + public static String prependIfMissingIgnoreCase(final String str, final CharSequence prefix, final CharSequence... prefixes) { + return prependIfMissing(str, prefix, true, prefixes); + } + + /** + * Converts a byte[] to a String using the specified character encoding. + * + * @param bytes + * the byte array to read from + * @param charsetName + * the encoding to use, if null then use the platform default + * @return a new String + * @throws UnsupportedEncodingException + * If the named charset is not supported + * @throws NullPointerException + * if the input is null + * @deprecated use {@link StringUtils#toEncodedString(byte[], Charset)} instead of String constants in your code + * @since 3.1 + */ + @Deprecated + public static String toString(final byte[] bytes, final String charsetName) throws UnsupportedEncodingException { + return charsetName != null ? new String(bytes, charsetName) : new String(bytes, Charset.defaultCharset()); + } + + /** + * Converts a byte[] to a String using the specified character encoding. + * + * @param bytes + * the byte array to read from + * @param charset + * the encoding to use, if null then use the platform default + * @return a new String + * @throws NullPointerException + * if {@code bytes} is null + * @since 3.2 + * @since 3.3 No longer throws {@link UnsupportedEncodingException}. + */ + public static String toEncodedString(final byte[] bytes, final Charset charset) { + return new String(bytes, charset != null ? charset : Charset.defaultCharset()); + } + + /** + *

              + * Wraps a string with a char. + *

              + * + *
              +     * StringUtils.wrap(null, *)        = null
              +     * StringUtils.wrap("", *)          = ""
              +     * StringUtils.wrap("ab", '\0')     = "ab"
              +     * StringUtils.wrap("ab", 'x')      = "xabx"
              +     * StringUtils.wrap("ab", '\'')     = "'ab'"
              +     * StringUtils.wrap("\"ab\"", '\"') = "\"\"ab\"\""
              +     * 
              + * + * @param str + * the string to be wrapped, may be {@code null} + * @param wrapWith + * the char that will wrap {@code str} + * @return the wrapped string, or {@code null} if {@code str==null} + * @since 3.4 + */ + public static String wrap(final String str, final char wrapWith) { + + if (isEmpty(str) || wrapWith == '\0') { + return str; + } + + return wrapWith + str + wrapWith; + } + + /** + *

              + * Wraps a String with another String. + *

              + * + *

              + * A {@code null} input String returns {@code null}. + *

              + * + *
              +     * StringUtils.wrap(null, *)         = null
              +     * StringUtils.wrap("", *)           = ""
              +     * StringUtils.wrap("ab", null)      = "ab"
              +     * StringUtils.wrap("ab", "x")       = "xabx"
              +     * StringUtils.wrap("ab", "\"")      = "\"ab\""
              +     * StringUtils.wrap("\"ab\"", "\"")  = "\"\"ab\"\""
              +     * StringUtils.wrap("ab", "'")       = "'ab'"
              +     * StringUtils.wrap("'abcd'", "'")   = "''abcd''"
              +     * StringUtils.wrap("\"abcd\"", "'") = "'\"abcd\"'"
              +     * StringUtils.wrap("'abcd'", "\"")  = "\"'abcd'\""
              +     * 
              + * + * @param str + * the String to be wrapper, may be null + * @param wrapWith + * the String that will wrap str + * @return wrapped String, {@code null} if null String input + * @since 3.4 + */ + public static String wrap(final String str, final String wrapWith) { + + if (isEmpty(str) || isEmpty(wrapWith)) { + return str; + } + + return wrapWith.concat(str).concat(wrapWith); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SystemUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SystemUtils.java new file mode 100644 index 000000000..99fdab5e6 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/SystemUtils.java @@ -0,0 +1,1663 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.io.File; + +/** + *

              + * Helpers for {@code java.lang.System}. + *

              + *

              + * If a system property cannot be read due to security restrictions, the corresponding field in this class will be set + * to {@code null} and a message will be written to {@code System.err}. + *

              + *

              + * #ThreadSafe# + *

              + * + * @since 1.0 + * @version $Id: SystemUtils.java 1671043 2015-04-03 12:09:07Z britter $ + */ +public class SystemUtils { + + /** + * The prefix String for all Windows OS. + */ + private static final String OS_NAME_WINDOWS_PREFIX = "Windows"; + + // System property constants + // ----------------------------------------------------------------------- + // These MUST be declared first. Other constants depend on this. + + /** + * The System property key for the user home directory. + */ + private static final String USER_HOME_KEY = "user.home"; + + /** + * The System property key for the user directory. + */ + private static final String USER_DIR_KEY = "user.dir"; + + /** + * The System property key for the Java IO temporary directory. + */ + private static final String JAVA_IO_TMPDIR_KEY = "java.io.tmpdir"; + + /** + * The System property key for the Java home directory. + */ + private static final String JAVA_HOME_KEY = "java.home"; + + /** + *

              + * The {@code awt.toolkit} System Property. + *

              + *

              + * Holds a class name, on Windows XP this is {@code sun.awt.windows.WToolkit}. + *

              + *

              + * On platforms without a GUI, this value is {@code null}. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.1 + */ + public static final String AWT_TOOLKIT = getSystemProperty("awt.toolkit"); + + /** + *

              + * The {@code file.encoding} System Property. + *

              + *

              + * File encoding, such as {@code Cp1252}. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.0 + * @since Java 1.2 + */ + public static final String FILE_ENCODING = getSystemProperty("file.encoding"); + + /** + *

              + * The {@code file.separator} System Property. + * The file separator is: + *

              + *
                + *
              • {@code "/"} on UNIX
              • + *
              • {@code "\"} on Windows.
              • + *
              + * + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String FILE_SEPARATOR = getSystemProperty("file.separator"); + + /** + *

              + * The {@code java.awt.fonts} System Property. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.1 + */ + public static final String JAVA_AWT_FONTS = getSystemProperty("java.awt.fonts"); + + /** + *

              + * The {@code java.awt.graphicsenv} System Property. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.1 + */ + public static final String JAVA_AWT_GRAPHICSENV = getSystemProperty("java.awt.graphicsenv"); + + /** + *

              + * The {@code java.awt.headless} System Property. The value of this property is the String {@code "true"} or + * {@code "false"}. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @see #isJavaAwtHeadless() + * @since 2.1 + * @since Java 1.4 + */ + public static final String JAVA_AWT_HEADLESS = getSystemProperty("java.awt.headless"); + + /** + *

              + * The {@code java.awt.printerjob} System Property. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.1 + */ + public static final String JAVA_AWT_PRINTERJOB = getSystemProperty("java.awt.printerjob"); + + /** + *

              + * The {@code java.class.path} System Property. Java class path. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String JAVA_CLASS_PATH = getSystemProperty("java.class.path"); + + /** + *

              + * The {@code java.class.version} System Property. Java class format version number. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String JAVA_CLASS_VERSION = getSystemProperty("java.class.version"); + + /** + *

              + * The {@code java.compiler} System Property. Name of JIT compiler to use. First in JDK version 1.2. Not used in Sun + * JDKs after 1.2. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2. Not used in Sun versions after 1.2. + */ + public static final String JAVA_COMPILER = getSystemProperty("java.compiler"); + + /** + *

              + * The {@code java.endorsed.dirs} System Property. Path of endorsed directory or directories. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.4 + */ + public static final String JAVA_ENDORSED_DIRS = getSystemProperty("java.endorsed.dirs"); + + /** + *

              + * The {@code java.ext.dirs} System Property. Path of extension directory or directories. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.3 + */ + public static final String JAVA_EXT_DIRS = getSystemProperty("java.ext.dirs"); + + /** + *

              + * The {@code java.home} System Property. Java installation directory. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String JAVA_HOME = getSystemProperty(JAVA_HOME_KEY); + + /** + *

              + * The {@code java.io.tmpdir} System Property. Default temp file path. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2 + */ + public static final String JAVA_IO_TMPDIR = getSystemProperty(JAVA_IO_TMPDIR_KEY); + + /** + *

              + * The {@code java.library.path} System Property. List of paths to search when loading libraries. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2 + */ + public static final String JAVA_LIBRARY_PATH = getSystemProperty("java.library.path"); + + /** + *

              + * The {@code java.runtime.name} System Property. Java Runtime Environment name. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.0 + * @since Java 1.3 + */ + public static final String JAVA_RUNTIME_NAME = getSystemProperty("java.runtime.name"); + + /** + *

              + * The {@code java.runtime.version} System Property. Java Runtime Environment version. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.0 + * @since Java 1.3 + */ + public static final String JAVA_RUNTIME_VERSION = getSystemProperty("java.runtime.version"); + + /** + *

              + * The {@code java.specification.name} System Property. Java Runtime Environment specification name. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2 + */ + public static final String JAVA_SPECIFICATION_NAME = getSystemProperty("java.specification.name"); + + /** + *

              + * The {@code java.specification.vendor} System Property. Java Runtime Environment specification vendor. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2 + */ + public static final String JAVA_SPECIFICATION_VENDOR = getSystemProperty("java.specification.vendor"); + + /** + *

              + * The {@code java.specification.version} System Property. Java Runtime Environment specification version. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.3 + */ + public static final String JAVA_SPECIFICATION_VERSION = getSystemProperty("java.specification.version"); + private static final JavaVersion JAVA_SPECIFICATION_VERSION_AS_ENUM = JavaVersion.get(JAVA_SPECIFICATION_VERSION); + + /** + *

              + * The {@code java.util.prefs.PreferencesFactory} System Property. A class name. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.1 + * @since Java 1.4 + */ + public static final String JAVA_UTIL_PREFS_PREFERENCES_FACTORY = + getSystemProperty("java.util.prefs.PreferencesFactory"); + + /** + *

              + * The {@code java.vendor} System Property. Java vendor-specific string. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String JAVA_VENDOR = getSystemProperty("java.vendor"); + + /** + *

              + * The {@code java.vendor.url} System Property. Java vendor URL. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String JAVA_VENDOR_URL = getSystemProperty("java.vendor.url"); + + /** + *

              + * The {@code java.version} System Property. Java version number. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String JAVA_VERSION = getSystemProperty("java.version"); + + /** + *

              + * The {@code java.vm.info} System Property. Java Virtual Machine implementation info. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.0 + * @since Java 1.2 + */ + public static final String JAVA_VM_INFO = getSystemProperty("java.vm.info"); + + /** + *

              + * The {@code java.vm.name} System Property. Java Virtual Machine implementation name. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2 + */ + public static final String JAVA_VM_NAME = getSystemProperty("java.vm.name"); + + /** + *

              + * The {@code java.vm.specification.name} System Property. Java Virtual Machine specification name. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2 + */ + public static final String JAVA_VM_SPECIFICATION_NAME = getSystemProperty("java.vm.specification.name"); + + /** + *

              + * The {@code java.vm.specification.vendor} System Property. Java Virtual Machine specification vendor. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2 + */ + public static final String JAVA_VM_SPECIFICATION_VENDOR = getSystemProperty("java.vm.specification.vendor"); + + /** + *

              + * The {@code java.vm.specification.version} System Property. Java Virtual Machine specification version. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2 + */ + public static final String JAVA_VM_SPECIFICATION_VERSION = getSystemProperty("java.vm.specification.version"); + + /** + *

              + * The {@code java.vm.vendor} System Property. Java Virtual Machine implementation vendor. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2 + */ + public static final String JAVA_VM_VENDOR = getSystemProperty("java.vm.vendor"); + + /** + *

              + * The {@code java.vm.version} System Property. Java Virtual Machine implementation version. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.2 + */ + public static final String JAVA_VM_VERSION = getSystemProperty("java.vm.version"); + + /** + *

              + * The {@code line.separator} System Property. Line separator ("\n" on UNIX). + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String LINE_SEPARATOR = getSystemProperty("line.separator"); + + /** + *

              + * The {@code os.arch} System Property. Operating system architecture. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String OS_ARCH = getSystemProperty("os.arch"); + + /** + *

              + * The {@code os.name} System Property. Operating system name. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String OS_NAME = getSystemProperty("os.name"); + + /** + *

              + * The {@code os.version} System Property. Operating system version. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String OS_VERSION = getSystemProperty("os.version"); + + /** + *

              + * The {@code path.separator} System Property. Path separator (":" on UNIX). + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String PATH_SEPARATOR = getSystemProperty("path.separator"); + + /** + *

              + * The {@code user.country} or {@code user.region} System Property. User's country code, such as {@code GB}. First + * in Java version 1.2 as {@code user.region}. Renamed to {@code user.country} in 1.4 + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.0 + * @since Java 1.2 + */ + public static final String USER_COUNTRY = getSystemProperty("user.country") == null ? + getSystemProperty("user.region") : getSystemProperty("user.country"); + + /** + *

              + * The {@code user.dir} System Property. User's current working directory. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String USER_DIR = getSystemProperty(USER_DIR_KEY); + + /** + *

              + * The {@code user.home} System Property. User's home directory. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String USER_HOME = getSystemProperty(USER_HOME_KEY); + + /** + *

              + * The {@code user.language} System Property. User's language code, such as {@code "en"}. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.0 + * @since Java 1.2 + */ + public static final String USER_LANGUAGE = getSystemProperty("user.language"); + + /** + *

              + * The {@code user.name} System Property. User's account name. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since Java 1.1 + */ + public static final String USER_NAME = getSystemProperty("user.name"); + + /** + *

              + * The {@code user.timezone} System Property. For example: {@code "America/Los_Angeles"}. + *

              + *

              + * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

              + *

              + * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or + * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of + * sync with that System property. + *

              + * + * @since 2.1 + */ + public static final String USER_TIMEZONE = getSystemProperty("user.timezone"); + + // Java version checks + // ----------------------------------------------------------------------- + // These MUST be declared after those above as they depend on the + // values being set up + + /** + *

              + * Is {@code true} if this is Java version 1.1 (also 1.1.x versions). + *

              + *

              + * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

              + */ + public static final boolean IS_JAVA_1_1 = getJavaVersionMatches("1.1"); + + /** + *

              + * Is {@code true} if this is Java version 1.2 (also 1.2.x versions). + *

              + *

              + * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

              + */ + public static final boolean IS_JAVA_1_2 = getJavaVersionMatches("1.2"); + + /** + *

              + * Is {@code true} if this is Java version 1.3 (also 1.3.x versions). + *

              + *

              + * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

              + */ + public static final boolean IS_JAVA_1_3 = getJavaVersionMatches("1.3"); + + /** + *

              + * Is {@code true} if this is Java version 1.4 (also 1.4.x versions). + *

              + *

              + * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

              + */ + public static final boolean IS_JAVA_1_4 = getJavaVersionMatches("1.4"); + + /** + *

              + * Is {@code true} if this is Java version 1.5 (also 1.5.x versions). + *

              + *

              + * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

              + */ + public static final boolean IS_JAVA_1_5 = getJavaVersionMatches("1.5"); + + /** + *

              + * Is {@code true} if this is Java version 1.6 (also 1.6.x versions). + *

              + *

              + * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

              + */ + public static final boolean IS_JAVA_1_6 = getJavaVersionMatches("1.6"); + + /** + *

              + * Is {@code true} if this is Java version 1.7 (also 1.7.x versions). + *

              + *

              + * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

              + * + * @since 3.0 + */ + public static final boolean IS_JAVA_1_7 = getJavaVersionMatches("1.7"); + + /** + *

              + * Is {@code true} if this is Java version 1.8 (also 1.8.x versions). + *

              + *

              + * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

              + * + * @since 3.3.2 + */ + public static final boolean IS_JAVA_1_8 = getJavaVersionMatches("1.8"); + + /** + *

              + * Is {@code true} if this is Java version 1.9 (also 1.9.x versions). + *

              + *

              + * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_JAVA_1_9 = getJavaVersionMatches("1.9"); + + // Operating system checks + // ----------------------------------------------------------------------- + // These MUST be declared after those above as they depend on the + // values being set up + // OS names from http://www.vamphq.com/os.html + // Selected ones included - please advise dev@commons.apache.org + // if you want another added or a mistake corrected + + /** + *

              + * Is {@code true} if this is AIX. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_AIX = getOSMatchesName("AIX"); + + /** + *

              + * Is {@code true} if this is HP-UX. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_HP_UX = getOSMatchesName("HP-UX"); + + /** + *

              + * Is {@code true} if this is IBM OS/400. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.3 + */ + public static final boolean IS_OS_400 = getOSMatchesName("OS/400"); + + /** + *

              + * Is {@code true} if this is Irix. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_IRIX = getOSMatchesName("Irix"); + + /** + *

              + * Is {@code true} if this is Linux. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_LINUX = getOSMatchesName("Linux") || getOSMatchesName("LINUX"); + + /** + *

              + * Is {@code true} if this is Mac. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_MAC = getOSMatchesName("Mac"); + + /** + *

              + * Is {@code true} if this is Mac. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_MAC_OSX = getOSMatchesName("Mac OS X"); + + /** + *

              + * Is {@code true} if this is Mac OS X Cheetah. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_CHEETAH = getOSMatches("Mac OS X", "10.0"); + + /** + *

              + * Is {@code true} if this is Mac OS X Puma. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_PUMA = getOSMatches("Mac OS X", "10.1"); + + /** + *

              + * Is {@code true} if this is Mac OS X Jaguar. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_JAGUAR = getOSMatches("Mac OS X", "10.2"); + + /** + *

              + * Is {@code true} if this is Mac OS X Panther. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_PANTHER = getOSMatches("Mac OS X", "10.3"); + + /** + *

              + * Is {@code true} if this is Mac OS X Tiger. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_TIGER = getOSMatches("Mac OS X", "10.4"); + + /** + *

              + * Is {@code true} if this is Mac OS X Leopard. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_LEOPARD = getOSMatches("Mac OS X", "10.5"); + + /** + *

              + * Is {@code true} if this is Mac OS X Snow Leopard. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_SNOW_LEOPARD = getOSMatches("Mac OS X", "10.6"); + + /** + *

              + * Is {@code true} if this is Mac OS X Lion. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_LION = getOSMatches("Mac OS X", "10.7"); + + /** + *

              + * Is {@code true} if this is Mac OS X Mountain Lion. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_MOUNTAIN_LION = getOSMatches("Mac OS X", "10.8"); + + /** + *

              + * Is {@code true} if this is Mac OS X Mavericks. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_MAVERICKS = getOSMatches("Mac OS X", "10.9"); + + /** + *

              + * Is {@code true} if this is Mac OS X Yosemite. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_MAC_OSX_YOSEMITE = getOSMatches("Mac OS X", "10.10"); + + /** + *

              + * Is {@code true} if this is FreeBSD. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.1 + */ + public static final boolean IS_OS_FREE_BSD = getOSMatchesName("FreeBSD"); + + /** + *

              + * Is {@code true} if this is OpenBSD. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.1 + */ + public static final boolean IS_OS_OPEN_BSD = getOSMatchesName("OpenBSD"); + + /** + *

              + * Is {@code true} if this is NetBSD. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.1 + */ + public static final boolean IS_OS_NET_BSD = getOSMatchesName("NetBSD"); + + /** + *

              + * Is {@code true} if this is OS/2. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_OS2 = getOSMatchesName("OS/2"); + + /** + *

              + * Is {@code true} if this is Solaris. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_SOLARIS = getOSMatchesName("Solaris"); + + /** + *

              + * Is {@code true} if this is SunOS. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_SUN_OS = getOSMatchesName("SunOS"); + + /** + *

              + * Is {@code true} if this is a UNIX like system, as in any of AIX, HP-UX, Irix, Linux, MacOSX, Solaris or SUN OS. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.1 + */ + public static final boolean IS_OS_UNIX = IS_OS_AIX || IS_OS_HP_UX || IS_OS_IRIX || IS_OS_LINUX || IS_OS_MAC_OSX + || IS_OS_SOLARIS || IS_OS_SUN_OS || IS_OS_FREE_BSD || IS_OS_OPEN_BSD || IS_OS_NET_BSD; + + /** + *

              + * Is {@code true} if this is Windows. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS = getOSMatchesName(OS_NAME_WINDOWS_PREFIX); + + /** + *

              + * Is {@code true} if this is Windows 2000. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_2000 = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " 2000"); + + /** + *

              + * Is {@code true} if this is Windows 2003. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.1 + */ + public static final boolean IS_OS_WINDOWS_2003 = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " 2003"); + + /** + *

              + * Is {@code true} if this is Windows Server 2008. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.1 + */ + public static final boolean IS_OS_WINDOWS_2008 = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " Server 2008"); + + /** + *

              + * Is {@code true} if this is Windows Server 2012. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.4 + */ + public static final boolean IS_OS_WINDOWS_2012 = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " Server 2012"); + + /** + *

              + * Is {@code true} if this is Windows 95. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_95 = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " 95"); + + /** + *

              + * Is {@code true} if this is Windows 98. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_98 = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " 98"); + + /** + *

              + * Is {@code true} if this is Windows ME. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_ME = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " Me"); + + /** + *

              + * Is {@code true} if this is Windows NT. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_NT = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " NT"); + + /** + *

              + * Is {@code true} if this is Windows XP. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_XP = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " XP"); + + // ----------------------------------------------------------------------- + /** + *

              + * Is {@code true} if this is Windows Vista. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 2.4 + */ + public static final boolean IS_OS_WINDOWS_VISTA = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " Vista"); + + /** + *

              + * Is {@code true} if this is Windows 7. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.0 + */ + public static final boolean IS_OS_WINDOWS_7 = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " 7"); + + /** + *

              + * Is {@code true} if this is Windows 8. + *

              + *

              + * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

              + * + * @since 3.2 + */ + public static final boolean IS_OS_WINDOWS_8 = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " 8"); + + /** + *

              + * Gets the Java home directory as a {@code File}. + *

              + * + * @return a directory + * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow + * access to the specified system property. + * @see System#getProperty(String) + * @since 2.1 + */ + public static File getJavaHome() { + return new File(System.getProperty(JAVA_HOME_KEY)); + } + + /** + *

              + * Gets the Java IO temporary directory as a {@code File}. + *

              + * + * @return a directory + * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow + * access to the specified system property. + * @see System#getProperty(String) + * @since 2.1 + */ + public static File getJavaIoTmpDir() { + return new File(System.getProperty(JAVA_IO_TMPDIR_KEY)); + } + + /** + *

              + * Decides if the Java version matches. + *

              + * + * @param versionPrefix the prefix for the java version + * @return true if matches, or false if not or can't determine + */ + private static boolean getJavaVersionMatches(final String versionPrefix) { + return isJavaVersionMatch(JAVA_SPECIFICATION_VERSION, versionPrefix); + } + + /** + * Decides if the operating system matches. + * + * @param osNamePrefix the prefix for the os name + * @param osVersionPrefix the prefix for the version + * @return true if matches, or false if not or can't determine + */ + private static boolean getOSMatches(final String osNamePrefix, final String osVersionPrefix) { + return isOSMatch(OS_NAME, OS_VERSION, osNamePrefix, osVersionPrefix); + } + + /** + * Decides if the operating system matches. + * + * @param osNamePrefix the prefix for the os name + * @return true if matches, or false if not or can't determine + */ + private static boolean getOSMatchesName(final String osNamePrefix) { + return isOSNameMatch(OS_NAME, osNamePrefix); + } + + // ----------------------------------------------------------------------- + /** + *

              + * Gets a System property, defaulting to {@code null} if the property cannot be read. + *

              + *

              + * If a {@code SecurityException} is caught, the return value is {@code null} and a message is written to + * {@code System.err}. + *

              + * + * @param property the system property name + * @return the system property value or {@code null} if a security problem occurs + */ + private static String getSystemProperty(final String property) { + try { + return System.getProperty(property); + } catch (final SecurityException ex) { + // we are not allowed to look at this property + System.err.println("Caught a SecurityException reading the system property '" + property + + "'; the SystemUtils property value will default to null."); + return null; + } + } + + /** + *

              + * Gets the user directory as a {@code File}. + *

              + * + * @return a directory + * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow + * access to the specified system property. + * @see System#getProperty(String) + * @since 2.1 + */ + public static File getUserDir() { + return new File(System.getProperty(USER_DIR_KEY)); + } + + /** + *

              + * Gets the user home directory as a {@code File}. + *

              + * + * @return a directory + * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow + * access to the specified system property. + * @see System#getProperty(String) + * @since 2.1 + */ + public static File getUserHome() { + return new File(System.getProperty(USER_HOME_KEY)); + } + + /** + * Returns whether the {@link #JAVA_AWT_HEADLESS} value is {@code true}. + * + * @return {@code true} if {@code JAVA_AWT_HEADLESS} is {@code "true"}, {@code false} otherwise. + * @see #JAVA_AWT_HEADLESS + * @since 2.1 + * @since Java 1.4 + */ + public static boolean isJavaAwtHeadless() { + return JAVA_AWT_HEADLESS != null ? JAVA_AWT_HEADLESS.equals(Boolean.TRUE.toString()) : false; + } + + /** + *

              + * Is the Java version at least the requested version. + *

              + *

              + * Example input: + *

              + *
                + *
              • {@code 1.2f} to test for Java 1.2
              • + *
              • {@code 1.31f} to test for Java 1.3.1
              • + *
              + * + * @param requiredVersion the required version, for example 1.31f + * @return {@code true} if the actual version is equal or greater than the required version + */ + public static boolean isJavaVersionAtLeast(final JavaVersion requiredVersion) { + return JAVA_SPECIFICATION_VERSION_AS_ENUM.atLeast(requiredVersion); + } + + /** + *

              + * Decides if the Java version matches. + *

              + *

              + * This method is package private instead of private to support unit test invocation. + *

              + * + * @param version the actual Java version + * @param versionPrefix the prefix for the expected Java version + * @return true if matches, or false if not or can't determine + */ + static boolean isJavaVersionMatch(final String version, final String versionPrefix) { + if (version == null) { + return false; + } + return version.startsWith(versionPrefix); + } + + /** + * Decides if the operating system matches. + *

              + * This method is package private instead of private to support unit test invocation. + *

              + * + * @param osName the actual OS name + * @param osVersion the actual OS version + * @param osNamePrefix the prefix for the expected OS name + * @param osVersionPrefix the prefix for the expected OS version + * @return true if matches, or false if not or can't determine + */ + static boolean isOSMatch(final String osName, final String osVersion, final String osNamePrefix, final String osVersionPrefix) { + if (osName == null || osVersion == null) { + return false; + } + return isOSNameMatch(osName, osNamePrefix) && isOSVersionMatch(osVersion, osVersionPrefix); + } + + /** + * Decides if the operating system matches. + *

              + * This method is package private instead of private to support unit test invocation. + *

              + * + * @param osName the actual OS name + * @param osNamePrefix the prefix for the expected OS name + * @return true if matches, or false if not or can't determine + */ + static boolean isOSNameMatch(final String osName, final String osNamePrefix) { + if (osName == null) { + return false; + } + return osName.startsWith(osNamePrefix); + } + + /** + * Decides if the operating system version matches. + *

              + * This method is package private instead of private to support unit test invocation. + *

              + * + * @param osVersion the actual OS version + * @param osVersionPrefix the prefix for the expected OS version + * @return true if matches, or false if not or can't determine + */ + static boolean isOSVersionMatch(final String osVersion, final String osVersionPrefix) { + if (StringUtils.isEmpty(osVersion)) { + return false; + } + // Compare parts of the version string instead of using String.startsWith(String) because otherwise + // osVersionPrefix 10.1 would also match osVersion 10.10 + String[] versionPrefixParts = osVersionPrefix.split("\\."); + String[] versionParts = osVersion.split("\\."); + for (int i = 0; i < Math.min(versionPrefixParts.length, versionParts.length); i++) { + if (!versionPrefixParts[i].equals(versionParts[i])) { + return false; + } + } + return true; + } + + // ----------------------------------------------------------------------- + /** + *

              + * SystemUtils instances should NOT be constructed in standard programming. Instead, the class should be used as + * {@code SystemUtils.FILE_SEPARATOR}. + *

              + *

              + * This constructor is public to permit tools that require a JavaBean instance to operate. + *

              + */ + public SystemUtils() { + super(); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Validate.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Validate.java new file mode 100644 index 000000000..ba94e059c --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/Validate.java @@ -0,0 +1,1256 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.regex.Pattern; + +/** + *

              This class assists in validating arguments. The validation methods are + * based along the following principles: + *

                + *
              • An invalid {@code null} argument causes a {@link NullPointerException}.
              • + *
              • A non-{@code null} argument causes an {@link IllegalArgumentException}.
              • + *
              • An invalid index into an array/collection/map/string causes an {@link IndexOutOfBoundsException}.
              • + *
              + * + *

              All exceptions messages are + * format strings + * as defined by the Java platform. For example:

              + * + *
              + * Validate.isTrue(i > 0, "The value must be greater than zero: %d", i);
              + * Validate.notNull(surname, "The surname must not be %s", null);
              + * 
              + * + *

              #ThreadSafe#

              + * @version $Id: Validate.java 1606051 2014-06-27 12:22:17Z ggregory $ + * @see java.lang.String#format(String, Object...) + * @since 2.0 + */ +public class Validate { + + private static final String DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE = + "The value %s is not in the specified exclusive range of %s to %s"; + private static final String DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE = + "The value %s is not in the specified inclusive range of %s to %s"; + private static final String DEFAULT_MATCHES_PATTERN_EX = "The string %s does not match the pattern %s"; + private static final String DEFAULT_IS_NULL_EX_MESSAGE = "The validated object is null"; + private static final String DEFAULT_IS_TRUE_EX_MESSAGE = "The validated expression is false"; + private static final String DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE = + "The validated array contains null element at index: %d"; + private static final String DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE = + "The validated collection contains null element at index: %d"; + private static final String DEFAULT_NOT_BLANK_EX_MESSAGE = "The validated character sequence is blank"; + private static final String DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE = "The validated array is empty"; + private static final String DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE = + "The validated character sequence is empty"; + private static final String DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE = "The validated collection is empty"; + private static final String DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE = "The validated map is empty"; + private static final String DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE = "The validated array index is invalid: %d"; + private static final String DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE = + "The validated character sequence index is invalid: %d"; + private static final String DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE = + "The validated collection index is invalid: %d"; + private static final String DEFAULT_VALID_STATE_EX_MESSAGE = "The validated state is false"; + private static final String DEFAULT_IS_ASSIGNABLE_EX_MESSAGE = "Cannot assign a %s to a %s"; + private static final String DEFAULT_IS_INSTANCE_OF_EX_MESSAGE = "Expected type: %s, actual: %s"; + + /** + * Constructor. This class should not normally be instantiated. + */ + public Validate() { + super(); + } + + // isTrue + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

              + * + *
              Validate.isTrue(i > 0.0, "The value must be greater than zero: %d", i);
              + * + *

              For performance reasons, the long value is passed as a separate parameter and + * appended to the exception message only in the case of an error.

              + * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param value the value to append to the message when invalid + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, double) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression, final String message, final long value) { + if (expression == false) { + throw new IllegalArgumentException(String.format(message, Long.valueOf(value))); + } + } + + /** + *

              Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

              + * + *
              Validate.isTrue(d > 0.0, "The value must be greater than zero: %s", d);
              + * + *

              For performance reasons, the double value is passed as a separate parameter and + * appended to the exception message only in the case of an error.

              + * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param value the value to append to the message when invalid + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression, final String message, final double value) { + if (expression == false) { + throw new IllegalArgumentException(String.format(message, Double.valueOf(value))); + } + } + + /** + *

              Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

              + * + *
              +     * Validate.isTrue(i >= min && i <= max, "The value must be between %d and %d", min, max);
              +     * Validate.isTrue(myObject.isOk(), "The object is not okay");
              + * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, double) + */ + public static void isTrue(final boolean expression, final String message, final Object... values) { + if (expression == false) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + /** + *

              Validate that the argument condition is {@code true}; otherwise + * throwing an exception. This method is useful when validating according + * to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

              + * + *
              +     * Validate.isTrue(i > 0);
              +     * Validate.isTrue(myObject.isOk());
              + * + *

              The message of the exception is "The validated expression is + * false".

              + * + * @param expression the boolean expression to check + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, double) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression) { + if (expression == false) { + throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE); + } + } + + // notNull + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument is not {@code null}; + * otherwise throwing an exception. + * + *

              Validate.notNull(myObject, "The object must not be null");
              + * + *

              The message of the exception is "The validated object is + * null".

              + * + * @param the object type + * @param object the object to check + * @return the validated object (never {@code null} for method chaining) + * @throws NullPointerException if the object is {@code null} + * @see #notNull(Object, String, Object...) + */ + public static T notNull(final T object) { + return notNull(object, DEFAULT_IS_NULL_EX_MESSAGE); + } + + /** + *

              Validate that the specified argument is not {@code null}; + * otherwise throwing an exception with the specified message. + * + *

              Validate.notNull(myObject, "The object must not be null");
              + * + * @param the object type + * @param object the object to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message + * @return the validated object (never {@code null} for method chaining) + * @throws NullPointerException if the object is {@code null} + * @see #notNull(Object) + */ + public static T notNull(final T object, final String message, final Object... values) { + if (object == null) { + throw new NullPointerException(String.format(message, values)); + } + return object; + } + + // notEmpty array + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument array is neither {@code null} + * nor a length of zero (no elements); otherwise throwing an exception + * with the specified message. + * + *

              Validate.notEmpty(myArray, "The array must not be empty");
              + * + * @param the array type + * @param array the array to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated array (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if the array is empty + * @see #notEmpty(Object[]) + */ + public static T[] notEmpty(final T[] array, final String message, final Object... values) { + if (array == null) { + throw new NullPointerException(String.format(message, values)); + } + if (array.length == 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + return array; + } + + /** + *

              Validate that the specified argument array is neither {@code null} + * nor a length of zero (no elements); otherwise throwing an exception. + * + *

              Validate.notEmpty(myArray);
              + * + *

              The message in the exception is "The validated array is + * empty". + * + * @param the array type + * @param array the array to check, validated not null by this method + * @return the validated array (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if the array is empty + * @see #notEmpty(Object[], String, Object...) + */ + public static T[] notEmpty(final T[] array) { + return notEmpty(array, DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); + } + + // notEmpty collection + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument collection is neither {@code null} + * nor a size of zero (no elements); otherwise throwing an exception + * with the specified message. + * + *

              Validate.notEmpty(myCollection, "The collection must not be empty");
              + * + * @param the collection type + * @param collection the collection to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated collection (never {@code null} method for chaining) + * @throws NullPointerException if the collection is {@code null} + * @throws IllegalArgumentException if the collection is empty + * @see #notEmpty(Object[]) + */ + public static > T notEmpty(final T collection, final String message, final Object... values) { + if (collection == null) { + throw new NullPointerException(String.format(message, values)); + } + if (collection.isEmpty()) { + throw new IllegalArgumentException(String.format(message, values)); + } + return collection; + } + + /** + *

              Validate that the specified argument collection is neither {@code null} + * nor a size of zero (no elements); otherwise throwing an exception. + * + *

              Validate.notEmpty(myCollection);
              + * + *

              The message in the exception is "The validated collection is + * empty".

              + * + * @param the collection type + * @param collection the collection to check, validated not null by this method + * @return the validated collection (never {@code null} method for chaining) + * @throws NullPointerException if the collection is {@code null} + * @throws IllegalArgumentException if the collection is empty + * @see #notEmpty(Collection, String, Object...) + */ + public static > T notEmpty(final T collection) { + return notEmpty(collection, DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE); + } + + // notEmpty map + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument map is neither {@code null} + * nor a size of zero (no elements); otherwise throwing an exception + * with the specified message. + * + *

              Validate.notEmpty(myMap, "The map must not be empty");
              + * + * @param the map type + * @param map the map to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated map (never {@code null} method for chaining) + * @throws NullPointerException if the map is {@code null} + * @throws IllegalArgumentException if the map is empty + * @see #notEmpty(Object[]) + */ + public static > T notEmpty(final T map, final String message, final Object... values) { + if (map == null) { + throw new NullPointerException(String.format(message, values)); + } + if (map.isEmpty()) { + throw new IllegalArgumentException(String.format(message, values)); + } + return map; + } + + /** + *

              Validate that the specified argument map is neither {@code null} + * nor a size of zero (no elements); otherwise throwing an exception. + * + *

              Validate.notEmpty(myMap);
              + * + *

              The message in the exception is "The validated map is + * empty".

              + * + * @param the map type + * @param map the map to check, validated not null by this method + * @return the validated map (never {@code null} method for chaining) + * @throws NullPointerException if the map is {@code null} + * @throws IllegalArgumentException if the map is empty + * @see #notEmpty(Map, String, Object...) + */ + public static > T notEmpty(final T map) { + return notEmpty(map, DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE); + } + + // notEmpty string + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument character sequence is + * neither {@code null} nor a length of zero (no characters); + * otherwise throwing an exception with the specified message. + * + *

              Validate.notEmpty(myString, "The string must not be empty");
              + * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated character sequence (never {@code null} method for chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IllegalArgumentException if the character sequence is empty + * @see #notEmpty(CharSequence) + */ + public static T notEmpty(final T chars, final String message, final Object... values) { + if (chars == null) { + throw new NullPointerException(String.format(message, values)); + } + if (chars.length() == 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + return chars; + } + + /** + *

              Validate that the specified argument character sequence is + * neither {@code null} nor a length of zero (no characters); + * otherwise throwing an exception with the specified message. + * + *

              Validate.notEmpty(myString);
              + * + *

              The message in the exception is "The validated + * character sequence is empty".

              + * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @return the validated character sequence (never {@code null} method for chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IllegalArgumentException if the character sequence is empty + * @see #notEmpty(CharSequence, String, Object...) + */ + public static T notEmpty(final T chars) { + return notEmpty(chars, DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE); + } + + // notBlank string + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument character sequence is + * neither {@code null}, a length of zero (no characters), empty + * nor whitespace; otherwise throwing an exception with the specified + * message. + * + *

              Validate.notBlank(myString, "The string must not be blank");
              + * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated character sequence (never {@code null} method for chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IllegalArgumentException if the character sequence is blank + * @see #notBlank(CharSequence) + * + * @since 3.0 + */ + public static T notBlank(final T chars, final String message, final Object... values) { + if (chars == null) { + throw new NullPointerException(String.format(message, values)); + } + if (StringUtils.isBlank(chars)) { + throw new IllegalArgumentException(String.format(message, values)); + } + return chars; + } + + /** + *

              Validate that the specified argument character sequence is + * neither {@code null}, a length of zero (no characters), empty + * nor whitespace; otherwise throwing an exception. + * + *

              Validate.notBlank(myString);
              + * + *

              The message in the exception is "The validated character + * sequence is blank".

              + * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @return the validated character sequence (never {@code null} method for chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IllegalArgumentException if the character sequence is blank + * @see #notBlank(CharSequence, String, Object...) + * + * @since 3.0 + */ + public static T notBlank(final T chars) { + return notBlank(chars, DEFAULT_NOT_BLANK_EX_MESSAGE); + } + + // noNullElements array + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument array is neither + * {@code null} nor contains any elements that are {@code null}; + * otherwise throwing an exception with the specified message. + * + *

              Validate.noNullElements(myArray, "The array contain null at position %d");
              + * + *

              If the array is {@code null}, then the message in the exception + * is "The validated object is null".

              + * + *

              If the array has a {@code null} element, then the iteration + * index of the invalid element is appended to the {@code values} + * argument.

              + * + * @param the array type + * @param array the array to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated array (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if an element is {@code null} + * @see #noNullElements(Object[]) + */ + public static T[] noNullElements(final T[] array, final String message, final Object... values) { + Validate.notNull(array); + for (int i = 0; i < array.length; i++) { + if (array[i] == null) { + final Object[] values2 = ArrayUtils.add(values, Integer.valueOf(i)); + throw new IllegalArgumentException(String.format(message, values2)); + } + } + return array; + } + + /** + *

              Validate that the specified argument array is neither + * {@code null} nor contains any elements that are {@code null}; + * otherwise throwing an exception.

              + * + *
              Validate.noNullElements(myArray);
              + * + *

              If the array is {@code null}, then the message in the exception + * is "The validated object is null".

              + * + *

              If the array has a {@code null} element, then the message in the + * exception is "The validated array contains null element at index: + * " followed by the index.

              + * + * @param the array type + * @param array the array to check, validated not null by this method + * @return the validated array (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if an element is {@code null} + * @see #noNullElements(Object[], String, Object...) + */ + public static T[] noNullElements(final T[] array) { + return noNullElements(array, DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE); + } + + // noNullElements iterable + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument iterable is neither + * {@code null} nor contains any elements that are {@code null}; + * otherwise throwing an exception with the specified message. + * + *

              Validate.noNullElements(myCollection, "The collection contains null at position %d");
              + * + *

              If the iterable is {@code null}, then the message in the exception + * is "The validated object is null".

              + * + *

              If the iterable has a {@code null} element, then the iteration + * index of the invalid element is appended to the {@code values} + * argument.

              + * + * @param the iterable type + * @param iterable the iterable to check, validated not null by this method + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated iterable (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if an element is {@code null} + * @see #noNullElements(Iterable) + */ + public static > T noNullElements(final T iterable, final String message, final Object... values) { + Validate.notNull(iterable); + int i = 0; + for (final Iterator it = iterable.iterator(); it.hasNext(); i++) { + if (it.next() == null) { + final Object[] values2 = ArrayUtils.addAll(values, Integer.valueOf(i)); + throw new IllegalArgumentException(String.format(message, values2)); + } + } + return iterable; + } + + /** + *

              Validate that the specified argument iterable is neither + * {@code null} nor contains any elements that are {@code null}; + * otherwise throwing an exception. + * + *

              Validate.noNullElements(myCollection);
              + * + *

              If the iterable is {@code null}, then the message in the exception + * is "The validated object is null".

              + * + *

              If the array has a {@code null} element, then the message in the + * exception is "The validated iterable contains null element at index: + * " followed by the index.

              + * + * @param the iterable type + * @param iterable the iterable to check, validated not null by this method + * @return the validated iterable (never {@code null} method for chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if an element is {@code null} + * @see #noNullElements(Iterable, String, Object...) + */ + public static > T noNullElements(final T iterable) { + return noNullElements(iterable, DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE); + } + + // validIndex array + //--------------------------------------------------------------------------------- + + /** + *

              Validates that the index is within the bounds of the argument + * array; otherwise throwing an exception with the specified message.

              + * + *
              Validate.validIndex(myArray, 2, "The array index is invalid: ");
              + * + *

              If the array is {@code null}, then the message of the exception + * is "The validated object is null".

              + * + * @param the array type + * @param array the array to check, validated not null by this method + * @param index the index to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated array (never {@code null} for method chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(Object[], int) + * + * @since 3.0 + */ + public static T[] validIndex(final T[] array, final int index, final String message, final Object... values) { + Validate.notNull(array); + if (index < 0 || index >= array.length) { + throw new IndexOutOfBoundsException(String.format(message, values)); + } + return array; + } + + /** + *

              Validates that the index is within the bounds of the argument + * array; otherwise throwing an exception.

              + * + *
              Validate.validIndex(myArray, 2);
              + * + *

              If the array is {@code null}, then the message of the exception + * is "The validated object is null".

              + * + *

              If the index is invalid, then the message of the exception is + * "The validated array index is invalid: " followed by the + * index.

              + * + * @param the array type + * @param array the array to check, validated not null by this method + * @param index the index to check + * @return the validated array (never {@code null} for method chaining) + * @throws NullPointerException if the array is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(Object[], int, String, Object...) + * + * @since 3.0 + */ + public static T[] validIndex(final T[] array, final int index) { + return validIndex(array, index, DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE, Integer.valueOf(index)); + } + + // validIndex collection + //--------------------------------------------------------------------------------- + + /** + *

              Validates that the index is within the bounds of the argument + * collection; otherwise throwing an exception with the specified message.

              + * + *
              Validate.validIndex(myCollection, 2, "The collection index is invalid: ");
              + * + *

              If the collection is {@code null}, then the message of the + * exception is "The validated object is null".

              + * + * @param the collection type + * @param collection the collection to check, validated not null by this method + * @param index the index to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated collection (never {@code null} for chaining) + * @throws NullPointerException if the collection is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(Collection, int) + * + * @since 3.0 + */ + public static > T validIndex(final T collection, final int index, final String message, final Object... values) { + Validate.notNull(collection); + if (index < 0 || index >= collection.size()) { + throw new IndexOutOfBoundsException(String.format(message, values)); + } + return collection; + } + + /** + *

              Validates that the index is within the bounds of the argument + * collection; otherwise throwing an exception.

              + * + *
              Validate.validIndex(myCollection, 2);
              + * + *

              If the index is invalid, then the message of the exception + * is "The validated collection index is invalid: " + * followed by the index.

              + * + * @param the collection type + * @param collection the collection to check, validated not null by this method + * @param index the index to check + * @return the validated collection (never {@code null} for method chaining) + * @throws NullPointerException if the collection is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(Collection, int, String, Object...) + * + * @since 3.0 + */ + public static > T validIndex(final T collection, final int index) { + return validIndex(collection, index, DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE, Integer.valueOf(index)); + } + + // validIndex string + //--------------------------------------------------------------------------------- + + /** + *

              Validates that the index is within the bounds of the argument + * character sequence; otherwise throwing an exception with the + * specified message.

              + * + *
              Validate.validIndex(myStr, 2, "The string index is invalid: ");
              + * + *

              If the character sequence is {@code null}, then the message + * of the exception is "The validated object is null".

              + * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @param index the index to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @return the validated character sequence (never {@code null} for method chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(CharSequence, int) + * + * @since 3.0 + */ + public static T validIndex(final T chars, final int index, final String message, final Object... values) { + Validate.notNull(chars); + if (index < 0 || index >= chars.length()) { + throw new IndexOutOfBoundsException(String.format(message, values)); + } + return chars; + } + + /** + *

              Validates that the index is within the bounds of the argument + * character sequence; otherwise throwing an exception.

              + * + *
              Validate.validIndex(myStr, 2);
              + * + *

              If the character sequence is {@code null}, then the message + * of the exception is "The validated object is + * null".

              + * + *

              If the index is invalid, then the message of the exception + * is "The validated character sequence index is invalid: " + * followed by the index.

              + * + * @param the character sequence type + * @param chars the character sequence to check, validated not null by this method + * @param index the index to check + * @return the validated character sequence (never {@code null} for method chaining) + * @throws NullPointerException if the character sequence is {@code null} + * @throws IndexOutOfBoundsException if the index is invalid + * @see #validIndex(CharSequence, int, String, Object...) + * + * @since 3.0 + */ + public static T validIndex(final T chars, final int index) { + return validIndex(chars, index, DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE, Integer.valueOf(index)); + } + + // validState + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the stateful condition is {@code true}; otherwise + * throwing an exception. This method is useful when validating according + * to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

              + * + *
              +     * Validate.validState(field > 0);
              +     * Validate.validState(this.isOk());
              + * + *

              The message of the exception is "The validated state is + * false".

              + * + * @param expression the boolean expression to check + * @throws IllegalStateException if expression is {@code false} + * @see #validState(boolean, String, Object...) + * + * @since 3.0 + */ + public static void validState(final boolean expression) { + if (expression == false) { + throw new IllegalStateException(DEFAULT_VALID_STATE_EX_MESSAGE); + } + } + + /** + *

              Validate that the stateful condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

              + * + *
              Validate.validState(this.isOk(), "The state is not OK: %s", myObject);
              + * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalStateException if expression is {@code false} + * @see #validState(boolean) + * + * @since 3.0 + */ + public static void validState(final boolean expression, final String message, final Object... values) { + if (expression == false) { + throw new IllegalStateException(String.format(message, values)); + } + } + + // matchesPattern + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument character sequence matches the specified regular + * expression pattern; otherwise throwing an exception.

              + * + *
              Validate.matchesPattern("hi", "[a-z]*");
              + * + *

              The syntax of the pattern is the one used in the {@link Pattern} class.

              + * + * @param input the character sequence to validate, not null + * @param pattern the regular expression pattern, not null + * @throws IllegalArgumentException if the character sequence does not match the pattern + * @see #matchesPattern(CharSequence, String, String, Object...) + * + * @since 3.0 + */ + public static void matchesPattern(final CharSequence input, final String pattern) { + // TODO when breaking BC, consider returning input + if (Pattern.matches(pattern, input) == false) { + throw new IllegalArgumentException(String.format(DEFAULT_MATCHES_PATTERN_EX, input, pattern)); + } + } + + /** + *

              Validate that the specified argument character sequence matches the specified regular + * expression pattern; otherwise throwing an exception with the specified message.

              + * + *
              Validate.matchesPattern("hi", "[a-z]*", "%s does not match %s", "hi" "[a-z]*");
              + * + *

              The syntax of the pattern is the one used in the {@link Pattern} class.

              + * + * @param input the character sequence to validate, not null + * @param pattern the regular expression pattern, not null + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if the character sequence does not match the pattern + * @see #matchesPattern(CharSequence, String) + * + * @since 3.0 + */ + public static void matchesPattern(final CharSequence input, final String pattern, final String message, final Object... values) { + // TODO when breaking BC, consider returning input + if (Pattern.matches(pattern, input) == false) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + // inclusiveBetween + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument object fall between the two + * inclusive values specified; otherwise, throws an exception.

              + * + *
              Validate.inclusiveBetween(0, 2, 1);
              + * + * @param the type of the argument object + * @param start the inclusive start value, not null + * @param end the inclusive end value, not null + * @param value the object to validate, not null + * @throws IllegalArgumentException if the value falls outside the boundaries + * @see #inclusiveBetween(Object, Object, Comparable, String, Object...) + * + * @since 3.0 + */ + public static void inclusiveBetween(final T start, final T end, final Comparable value) { + // TODO when breaking BC, consider returning value + if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { + throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + /** + *

              Validate that the specified argument object fall between the two + * inclusive values specified; otherwise, throws an exception with the + * specified message.

              + * + *
              Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");
              + * + * @param the type of the argument object + * @param start the inclusive start value, not null + * @param end the inclusive end value, not null + * @param value the object to validate, not null + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if the value falls outside the boundaries + * @see #inclusiveBetween(Object, Object, Comparable) + * + * @since 3.0 + */ + public static void inclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { + // TODO when breaking BC, consider returning value + if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + /** + * Validate that the specified primitive value falls between the two + * inclusive values specified; otherwise, throws an exception. + * + *
              Validate.inclusiveBetween(0, 2, 1);
              + * + * @param start the inclusive start value + * @param end the inclusive end value + * @param value the value to validate + * @throws IllegalArgumentException if the value falls outside the boundaries (inclusive) + * + * @since 3.3 + */ + @SuppressWarnings("boxing") + public static void inclusiveBetween(final long start, final long end, final long value) { + // TODO when breaking BC, consider returning value + if (value < start || value > end) { + throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + /** + * Validate that the specified primitive value falls between the two + * inclusive values specified; otherwise, throws an exception with the + * specified message. + * + *
              Validate.inclusiveBetween(0, 2, 1, "Not in range");
              + * + * @param start the inclusive start value + * @param end the inclusive end value + * @param value the value to validate + * @param message the exception message if invalid, not null + * + * @throws IllegalArgumentException if the value falls outside the boundaries + * + * @since 3.3 + */ + public static void inclusiveBetween(final long start, final long end, final long value, final String message) { + // TODO when breaking BC, consider returning value + if (value < start || value > end) { + throw new IllegalArgumentException(String.format(message)); + } + } + + /** + * Validate that the specified primitive value falls between the two + * inclusive values specified; otherwise, throws an exception. + * + *
              Validate.inclusiveBetween(0.1, 2.1, 1.1);
              + * + * @param start the inclusive start value + * @param end the inclusive end value + * @param value the value to validate + * @throws IllegalArgumentException if the value falls outside the boundaries (inclusive) + * + * @since 3.3 + */ + @SuppressWarnings("boxing") + public static void inclusiveBetween(final double start, final double end, final double value) { + // TODO when breaking BC, consider returning value + if (value < start || value > end) { + throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + /** + * Validate that the specified primitive value falls between the two + * inclusive values specified; otherwise, throws an exception with the + * specified message. + * + *
              Validate.inclusiveBetween(0.1, 2.1, 1.1, "Not in range");
              + * + * @param start the inclusive start value + * @param end the inclusive end value + * @param value the value to validate + * @param message the exception message if invalid, not null + * + * @throws IllegalArgumentException if the value falls outside the boundaries + * + * @since 3.3 + */ + public static void inclusiveBetween(final double start, final double end, final double value, final String message) { + // TODO when breaking BC, consider returning value + if (value < start || value > end) { + throw new IllegalArgumentException(String.format(message)); + } + } + + // exclusiveBetween + //--------------------------------------------------------------------------------- + + /** + *

              Validate that the specified argument object fall between the two + * exclusive values specified; otherwise, throws an exception.

              + * + *
              Validate.exclusiveBetween(0, 2, 1);
              + * + * @param the type of the argument object + * @param start the exclusive start value, not null + * @param end the exclusive end value, not null + * @param value the object to validate, not null + * @throws IllegalArgumentException if the value falls outside the boundaries + * @see #exclusiveBetween(Object, Object, Comparable, String, Object...) + * + * @since 3.0 + */ + public static void exclusiveBetween(final T start, final T end, final Comparable value) { + // TODO when breaking BC, consider returning value + if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { + throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + /** + *

              Validate that the specified argument object fall between the two + * exclusive values specified; otherwise, throws an exception with the + * specified message.

              + * + *
              Validate.exclusiveBetween(0, 2, 1, "Not in boundaries");
              + * + * @param the type of the argument object + * @param start the exclusive start value, not null + * @param end the exclusive end value, not null + * @param value the object to validate, not null + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if the value falls outside the boundaries + * @see #exclusiveBetween(Object, Object, Comparable) + * + * @since 3.0 + */ + public static void exclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { + // TODO when breaking BC, consider returning value + if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + /** + * Validate that the specified primitive value falls between the two + * exclusive values specified; otherwise, throws an exception. + * + *
              Validate.exclusiveBetween(0, 2, 1);
              + * + * @param start the exclusive start value + * @param end the exclusive end value + * @param value the value to validate + * @throws IllegalArgumentException if the value falls out of the boundaries + * + * @since 3.3 + */ + @SuppressWarnings("boxing") + public static void exclusiveBetween(final long start, final long end, final long value) { + // TODO when breaking BC, consider returning value + if (value <= start || value >= end) { + throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + /** + * Validate that the specified primitive value falls between the two + * exclusive values specified; otherwise, throws an exception with the + * specified message. + * + *
              Validate.exclusiveBetween(0, 2, 1, "Not in range");
              + * + * @param start the exclusive start value + * @param end the exclusive end value + * @param value the value to validate + * @param message the exception message if invalid, not null + * + * @throws IllegalArgumentException if the value falls outside the boundaries + * + * @since 3.3 + */ + public static void exclusiveBetween(final long start, final long end, final long value, final String message) { + // TODO when breaking BC, consider returning value + if (value <= start || value >= end) { + throw new IllegalArgumentException(String.format(message)); + } + } + + /** + * Validate that the specified primitive value falls between the two + * exclusive values specified; otherwise, throws an exception. + * + *
              Validate.exclusiveBetween(0.1, 2.1, 1.1);
              + * + * @param start the exclusive start value + * @param end the exclusive end value + * @param value the value to validate + * @throws IllegalArgumentException if the value falls out of the boundaries + * + * @since 3.3 + */ + @SuppressWarnings("boxing") + public static void exclusiveBetween(final double start, final double end, final double value) { + // TODO when breaking BC, consider returning value + if (value <= start || value >= end) { + throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + /** + * Validate that the specified primitive value falls between the two + * exclusive values specified; otherwise, throws an exception with the + * specified message. + * + *
              Validate.exclusiveBetween(0.1, 2.1, 1.1, "Not in range");
              + * + * @param start the exclusive start value + * @param end the exclusive end value + * @param value the value to validate + * @param message the exception message if invalid, not null + * + * @throws IllegalArgumentException if the value falls outside the boundaries + * + * @since 3.3 + */ + public static void exclusiveBetween(final double start, final double end, final double value, final String message) { + // TODO when breaking BC, consider returning value + if (value <= start || value >= end) { + throw new IllegalArgumentException(String.format(message)); + } + } + + // isInstanceOf + //--------------------------------------------------------------------------------- + + /** + * Validates that the argument is an instance of the specified class, if not throws an exception. + * + *

              This method is useful when validating according to an arbitrary class

              + * + *
              Validate.isInstanceOf(OkClass.class, object);
              + * + *

              The message of the exception is "Expected type: {type}, actual: {obj_type}"

              + * + * @param type the class the object must be validated against, not null + * @param obj the object to check, null throws an exception + * @throws IllegalArgumentException if argument is not of specified class + * @see #isInstanceOf(Class, Object, String, Object...) + * + * @since 3.0 + */ + public static void isInstanceOf(final Class type, final Object obj) { + // TODO when breaking BC, consider returning obj + if (type.isInstance(obj) == false) { + throw new IllegalArgumentException(String.format(DEFAULT_IS_INSTANCE_OF_EX_MESSAGE, type.getName(), + obj == null ? "null" : obj.getClass().getName())); + } + } + + /** + *

              Validate that the argument is an instance of the specified class; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary class

              + * + *
              Validate.isInstanceOf(OkClass.classs, object, "Wrong class, object is of class %s",
              +     *   object.getClass().getName());
              + * + * @param type the class the object must be validated against, not null + * @param obj the object to check, null throws an exception + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if argument is not of specified class + * @see #isInstanceOf(Class, Object) + * + * @since 3.0 + */ + public static void isInstanceOf(final Class type, final Object obj, final String message, final Object... values) { + // TODO when breaking BC, consider returning obj + if (type.isInstance(obj) == false) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + // isAssignableFrom + //--------------------------------------------------------------------------------- + + /** + * Validates that the argument can be converted to the specified class, if not, throws an exception. + * + *

              This method is useful when validating that there will be no casting errors.

              + * + *
              Validate.isAssignableFrom(SuperClass.class, object.getClass());
              + * + *

              The message format of the exception is "Cannot assign {type} to {superType}"

              + * + * @param superType the class the class must be validated against, not null + * @param type the class to check, not null + * @throws IllegalArgumentException if type argument is not assignable to the specified superType + * @see #isAssignableFrom(Class, Class, String, Object...) + * + * @since 3.0 + */ + public static void isAssignableFrom(final Class superType, final Class type) { + // TODO when breaking BC, consider returning type + if (superType.isAssignableFrom(type) == false) { + throw new IllegalArgumentException(String.format(DEFAULT_IS_ASSIGNABLE_EX_MESSAGE, type == null ? "null" : type.getName(), + superType.getName())); + } + } + + /** + * Validates that the argument can be converted to the specified class, if not throws an exception. + * + *

              This method is useful when validating if there will be no casting errors.

              + * + *
              Validate.isAssignableFrom(SuperClass.class, object.getClass());
              + * + *

              The message of the exception is "The validated object can not be converted to the" + * followed by the name of the class and "class"

              + * + * @param superType the class the class must be validated against, not null + * @param type the class to check, not null + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if argument can not be converted to the specified class + * @see #isAssignableFrom(Class, Class) + */ + public static void isAssignableFrom(final Class superType, final Class type, final String message, final Object... values) { + // TODO when breaking BC, consider returning type + if (superType.isAssignableFrom(type) == false) { + throw new IllegalArgumentException(String.format(message, values)); + } + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Builder.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Builder.java new file mode 100644 index 000000000..6ebf0d1fc --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Builder.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +/** + *

              + * The Builder interface is designed to designate a class as a builder + * object in the Builder design pattern. Builders are capable of creating and + * configuring objects or results that normally take multiple steps to construct + * or are very complex to derive. + *

              + * + *

              + * The builder interface defines a single method, {@link #build()}, that + * classes must implement. The result of this method should be the final + * configured object or result after all building operations are performed. + *

              + * + *

              + * It is a recommended practice that the methods supplied to configure the + * object or result being built return a reference to {@code this} so that + * method calls can be chained together. + *

              + * + *

              + * Example Builder: + *

              
              + * class FontBuilder implements Builder<Font> {
              + *     private Font font;
              + *     
              + *     public FontBuilder(String fontName) {
              + *         this.font = new Font(fontName, Font.PLAIN, 12);
              + *     }
              + * 
              + *     public FontBuilder bold() {
              + *         this.font = this.font.deriveFont(Font.BOLD);
              + *         return this; // Reference returned so calls can be chained
              + *     }
              + *     
              + *     public FontBuilder size(float pointSize) {
              + *         this.font = this.font.deriveFont(pointSize);
              + *         return this; // Reference returned so calls can be chained
              + *     }
              + * 
              + *     // Other Font construction methods
              + * 
              + *     public Font build() {
              + *         return this.font;
              + *     }
              + * }
              + * 
              + * + * Example Builder Usage: + *
              
              + * Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
              + *                                                              .size(14.0f)
              + *                                                              .build();
              + * 
              + * + * + * @param the type of object that the builder will construct or compute. + * + * @since 3.0 + * @version $Id: Builder.java 1583482 2014-03-31 22:54:57Z niallp $ + */ +public interface Builder { + + /** + * Returns a reference to the object being constructed or result being + * calculated by the builder. + * + * @return the object constructed or result calculated by the builder. + */ + T build(); +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/CompareToBuilder.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/CompareToBuilder.java new file mode 100644 index 000000000..8e4e34c23 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/CompareToBuilder.java @@ -0,0 +1,1029 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Comparator; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; + +/** + * Assists in implementing {@link java.lang.Comparable#compareTo(Object)} methods. + * + *

              It is consistent with equals(Object) and + * hashcode() built with {@link EqualsBuilder} and + * {@link HashCodeBuilder}.

              + * + *

              Two Objects that compare equal using equals(Object) should normally + * also compare equal using compareTo(Object).

              + * + *

              All relevant fields should be included in the calculation of the + * comparison. Derived fields may be ignored. The same fields, in the same + * order, should be used in both compareTo(Object) and + * equals(Object).

              + * + *

              To use this class write code as follows:

              + * + *
              + * public class MyClass {
              + *   String field1;
              + *   int field2;
              + *   boolean field3;
              + *
              + *   ...
              + *
              + *   public int compareTo(Object o) {
              + *     MyClass myClass = (MyClass) o;
              + *     return new CompareToBuilder()
              + *       .appendSuper(super.compareTo(o)
              + *       .append(this.field1, myClass.field1)
              + *       .append(this.field2, myClass.field2)
              + *       .append(this.field3, myClass.field3)
              + *       .toComparison();
              + *   }
              + * }
              + * 
              + * + *

              Values are compared in the order they are appended to the builder. If any comparison returns + * a non-zero result, then that value will be the result returned by {@code toComparison()} and all + * subsequent comparisons are skipped.

              + * + *

              Alternatively, there are {@link #reflectionCompare(Object, Object) reflectionCompare} methods that use + * reflection to determine the fields to append. Because fields can be private, + * reflectionCompare uses {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} to + * bypass normal access control checks. This will fail under a security manager, + * unless the appropriate permissions are set up correctly. It is also + * slower than appending explicitly.

              + * + *

              A typical implementation of compareTo(Object) using + * reflectionCompare looks like:

              + + *
              + * public int compareTo(Object o) {
              + *   return CompareToBuilder.reflectionCompare(this, o);
              + * }
              + * 
              + * + *

              The reflective methods compare object fields in the order returned by + * {@link Class#getDeclaredFields()}. The fields of the class are compared first, followed by those + * of its parent classes (in order from the bottom to the top of the class hierarchy).

              + * + * @see java.lang.Comparable + * @see java.lang.Object#equals(Object) + * @see java.lang.Object#hashCode() + * @see EqualsBuilder + * @see HashCodeBuilder + * @since 1.0 + * @version $Id: CompareToBuilder.java 1628165 2014-09-29 12:02:11Z djones $ + */ +public class CompareToBuilder implements Builder { + + /** + * Current state of the comparison as appended fields are checked. + */ + private int comparison; + + /** + *

              Constructor for CompareToBuilder.

              + * + *

              Starts off assuming that the objects are equal. Multiple calls are + * then made to the various append methods, followed by a call to + * {@link #toComparison} to get the result.

              + */ + public CompareToBuilder() { + super(); + comparison = 0; + } + + //----------------------------------------------------------------------- + /** + *

              Compares two Objects via reflection.

              + * + *

              Fields can be private, thus AccessibleObject.setAccessible + * is used to bypass normal access control checks. This will fail under a + * security manager unless the appropriate permissions are set.

              + * + *
                + *
              • Static fields will not be compared
              • + *
              • Transient members will be not be compared, as they are likely derived + * fields
              • + *
              • Superclass fields will be compared
              • + *
              + * + *

              If both lhs and rhs are null, + * they are considered equal.

              + * + * @param lhs left-hand object + * @param rhs right-hand object + * @return a negative integer, zero, or a positive integer as lhs + * is less than, equal to, or greater than rhs + * @throws NullPointerException if either (but not both) parameters are + * null + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + */ + public static int reflectionCompare(final Object lhs, final Object rhs) { + return reflectionCompare(lhs, rhs, false, null); + } + + /** + *

              Compares two Objects via reflection.

              + * + *

              Fields can be private, thus AccessibleObject.setAccessible + * is used to bypass normal access control checks. This will fail under a + * security manager unless the appropriate permissions are set.

              + * + *
                + *
              • Static fields will not be compared
              • + *
              • If compareTransients is true, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
              • + *
              • Superclass fields will be compared
              • + *
              + * + *

              If both lhs and rhs are null, + * they are considered equal.

              + * + * @param lhs left-hand object + * @param rhs right-hand object + * @param compareTransients whether to compare transient fields + * @return a negative integer, zero, or a positive integer as lhs + * is less than, equal to, or greater than rhs + * @throws NullPointerException if either lhs or rhs + * (but not both) is null + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + */ + public static int reflectionCompare(final Object lhs, final Object rhs, final boolean compareTransients) { + return reflectionCompare(lhs, rhs, compareTransients, null); + } + + /** + *

              Compares two Objects via reflection.

              + * + *

              Fields can be private, thus AccessibleObject.setAccessible + * is used to bypass normal access control checks. This will fail under a + * security manager unless the appropriate permissions are set.

              + * + *
                + *
              • Static fields will not be compared
              • + *
              • If compareTransients is true, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
              • + *
              • Superclass fields will be compared
              • + *
              + * + *

              If both lhs and rhs are null, + * they are considered equal.

              + * + * @param lhs left-hand object + * @param rhs right-hand object + * @param excludeFields Collection of String fields to exclude + * @return a negative integer, zero, or a positive integer as lhs + * is less than, equal to, or greater than rhs + * @throws NullPointerException if either lhs or rhs + * (but not both) is null + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + * @since 2.2 + */ + public static int reflectionCompare(final Object lhs, final Object rhs, final Collection excludeFields) { + return reflectionCompare(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); + } + + /** + *

              Compares two Objects via reflection.

              + * + *

              Fields can be private, thus AccessibleObject.setAccessible + * is used to bypass normal access control checks. This will fail under a + * security manager unless the appropriate permissions are set.

              + * + *
                + *
              • Static fields will not be compared
              • + *
              • If compareTransients is true, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
              • + *
              • Superclass fields will be compared
              • + *
              + * + *

              If both lhs and rhs are null, + * they are considered equal.

              + * + * @param lhs left-hand object + * @param rhs right-hand object + * @param excludeFields array of fields to exclude + * @return a negative integer, zero, or a positive integer as lhs + * is less than, equal to, or greater than rhs + * @throws NullPointerException if either lhs or rhs + * (but not both) is null + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + * @since 2.2 + */ + public static int reflectionCompare(final Object lhs, final Object rhs, final String... excludeFields) { + return reflectionCompare(lhs, rhs, false, null, excludeFields); + } + + /** + *

              Compares two Objects via reflection.

              + * + *

              Fields can be private, thus AccessibleObject.setAccessible + * is used to bypass normal access control checks. This will fail under a + * security manager unless the appropriate permissions are set.

              + * + *
                + *
              • Static fields will not be compared
              • + *
              • If the compareTransients is true, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
              • + *
              • Compares superclass fields up to and including reflectUpToClass. + * If reflectUpToClass is null, compares all superclass fields.
              • + *
              + * + *

              If both lhs and rhs are null, + * they are considered equal.

              + * + * @param lhs left-hand object + * @param rhs right-hand object + * @param compareTransients whether to compare transient fields + * @param reflectUpToClass last superclass for which fields are compared + * @param excludeFields fields to exclude + * @return a negative integer, zero, or a positive integer as lhs + * is less than, equal to, or greater than rhs + * @throws NullPointerException if either lhs or rhs + * (but not both) is null + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + * @since 2.2 (2.0 as reflectionCompare(Object, Object, boolean, Class)) + */ + public static int reflectionCompare( + final Object lhs, + final Object rhs, + final boolean compareTransients, + final Class reflectUpToClass, + final String... excludeFields) { + + if (lhs == rhs) { + return 0; + } + if (lhs == null || rhs == null) { + throw new NullPointerException(); + } + Class lhsClazz = lhs.getClass(); + if (!lhsClazz.isInstance(rhs)) { + throw new ClassCastException(); + } + final CompareToBuilder compareToBuilder = new CompareToBuilder(); + reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields); + while (lhsClazz.getSuperclass() != null && lhsClazz != reflectUpToClass) { + lhsClazz = lhsClazz.getSuperclass(); + reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields); + } + return compareToBuilder.toComparison(); + } + + /** + *

              Appends to builder the comparison of lhs + * to rhs using the fields defined in clazz.

              + * + * @param lhs left-hand object + * @param rhs right-hand object + * @param clazz Class that defines fields to be compared + * @param builder CompareToBuilder to append to + * @param useTransients whether to compare transient fields + * @param excludeFields fields to exclude + */ + private static void reflectionAppend( + final Object lhs, + final Object rhs, + final Class clazz, + final CompareToBuilder builder, + final boolean useTransients, + final String[] excludeFields) { + + final Field[] fields = clazz.getDeclaredFields(); + AccessibleObject.setAccessible(fields, true); + for (int i = 0; i < fields.length && builder.comparison == 0; i++) { + final Field f = fields[i]; + if (!ArrayUtils.contains(excludeFields, f.getName()) + && (f.getName().indexOf('$') == -1) + && (useTransients || !Modifier.isTransient(f.getModifiers())) + && (!Modifier.isStatic(f.getModifiers()))) { + try { + builder.append(f.get(lhs), f.get(rhs)); + } catch (final IllegalAccessException e) { + // This can't happen. Would get a Security exception instead. + // Throw a runtime exception in case the impossible happens. + throw new InternalError("Unexpected IllegalAccessException"); + } + } + } + } + + //----------------------------------------------------------------------- + /** + *

              Appends to the builder the compareTo(Object) + * result of the superclass.

              + * + * @param superCompareTo result of calling super.compareTo(Object) + * @return this - used to chain append calls + * @since 2.0 + */ + public CompareToBuilder appendSuper(final int superCompareTo) { + if (comparison != 0) { + return this; + } + comparison = superCompareTo; + return this; + } + + //----------------------------------------------------------------------- + /** + *

              Appends to the builder the comparison of + * two Objects.

              + * + *
                + *
              1. Check if lhs == rhs
              2. + *
              3. Check if either lhs or rhs is null, + * a null object is less than a non-null object
              4. + *
              5. Check the object contents
              6. + *
              + * + *

              lhs must either be an array or implement {@link Comparable}.

              + * + * @param lhs left-hand object + * @param rhs right-hand object + * @return this - used to chain append calls + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + */ + public CompareToBuilder append(final Object lhs, final Object rhs) { + return append(lhs, rhs, null); + } + + /** + *

              Appends to the builder the comparison of + * two Objects.

              + * + *
                + *
              1. Check if lhs == rhs
              2. + *
              3. Check if either lhs or rhs is null, + * a null object is less than a non-null object
              4. + *
              5. Check the object contents
              6. + *
              + * + *

              If lhs is an array, array comparison methods will be used. + * Otherwise comparator will be used to compare the objects. + * If comparator is null, lhs must + * implement {@link Comparable} instead.

              + * + * @param lhs left-hand object + * @param rhs right-hand object + * @param comparator Comparator used to compare the objects, + * null means treat lhs as Comparable + * @return this - used to chain append calls + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + * @since 2.0 + */ + public CompareToBuilder append(final Object lhs, final Object rhs, final Comparator comparator) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null) { + comparison = -1; + return this; + } + if (rhs == null) { + comparison = +1; + return this; + } + if (lhs.getClass().isArray()) { + // switch on type of array, to dispatch to the correct handler + // handles multi dimensional arrays + // throws a ClassCastException if rhs is not the correct array type + if (lhs instanceof long[]) { + append((long[]) lhs, (long[]) rhs); + } else if (lhs instanceof int[]) { + append((int[]) lhs, (int[]) rhs); + } else if (lhs instanceof short[]) { + append((short[]) lhs, (short[]) rhs); + } else if (lhs instanceof char[]) { + append((char[]) lhs, (char[]) rhs); + } else if (lhs instanceof byte[]) { + append((byte[]) lhs, (byte[]) rhs); + } else if (lhs instanceof double[]) { + append((double[]) lhs, (double[]) rhs); + } else if (lhs instanceof float[]) { + append((float[]) lhs, (float[]) rhs); + } else if (lhs instanceof boolean[]) { + append((boolean[]) lhs, (boolean[]) rhs); + } else { + // not an array of primitives + // throws a ClassCastException if rhs is not an array + append((Object[]) lhs, (Object[]) rhs, comparator); + } + } else { + // the simple case, not an array, just test the element + if (comparator == null) { + @SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc + final Comparable comparable = (Comparable) lhs; + comparison = comparable.compareTo(rhs); + } else { + @SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc + final Comparator comparator2 = (Comparator) comparator; + comparison = comparator2.compare(lhs, rhs); + } + } + return this; + } + + //------------------------------------------------------------------------- + /** + * Appends to the builder the comparison of + * two longs. + * + * @param lhs left-hand value + * @param rhs right-hand value + * @return this - used to chain append calls + */ + public CompareToBuilder append(final long lhs, final long rhs) { + if (comparison != 0) { + return this; + } + comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + return this; + } + + /** + * Appends to the builder the comparison of + * two ints. + * + * @param lhs left-hand value + * @param rhs right-hand value + * @return this - used to chain append calls + */ + public CompareToBuilder append(final int lhs, final int rhs) { + if (comparison != 0) { + return this; + } + comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + return this; + } + + /** + * Appends to the builder the comparison of + * two shorts. + * + * @param lhs left-hand value + * @param rhs right-hand value + * @return this - used to chain append calls + */ + public CompareToBuilder append(final short lhs, final short rhs) { + if (comparison != 0) { + return this; + } + comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + return this; + } + + /** + * Appends to the builder the comparison of + * two chars. + * + * @param lhs left-hand value + * @param rhs right-hand value + * @return this - used to chain append calls + */ + public CompareToBuilder append(final char lhs, final char rhs) { + if (comparison != 0) { + return this; + } + comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + return this; + } + + /** + * Appends to the builder the comparison of + * two bytes. + * + * @param lhs left-hand value + * @param rhs right-hand value + * @return this - used to chain append calls + */ + public CompareToBuilder append(final byte lhs, final byte rhs) { + if (comparison != 0) { + return this; + } + comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + return this; + } + + /** + *

              Appends to the builder the comparison of + * two doubles.

              + * + *

              This handles NaNs, Infinities, and -0.0.

              + * + *

              It is compatible with the hash code generated by + * HashCodeBuilder.

              + * + * @param lhs left-hand value + * @param rhs right-hand value + * @return this - used to chain append calls + */ + public CompareToBuilder append(final double lhs, final double rhs) { + if (comparison != 0) { + return this; + } + comparison = Double.compare(lhs, rhs); + return this; + } + + /** + *

              Appends to the builder the comparison of + * two floats.

              + * + *

              This handles NaNs, Infinities, and -0.0.

              + * + *

              It is compatible with the hash code generated by + * HashCodeBuilder.

              + * + * @param lhs left-hand value + * @param rhs right-hand value + * @return this - used to chain append calls + */ + public CompareToBuilder append(final float lhs, final float rhs) { + if (comparison != 0) { + return this; + } + comparison = Float.compare(lhs, rhs); + return this; + } + + /** + * Appends to the builder the comparison of + * two booleanss. + * + * @param lhs left-hand value + * @param rhs right-hand value + * @return this - used to chain append calls + */ + public CompareToBuilder append(final boolean lhs, final boolean rhs) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == false) { + comparison = -1; + } else { + comparison = +1; + } + return this; + } + + //----------------------------------------------------------------------- + /** + *

              Appends to the builder the deep comparison of + * two Object arrays.

              + * + *
                + *
              1. Check if arrays are the same using ==
              2. + *
              3. Check if for null, null is less than non-null
              4. + *
              5. Check array length, a short length array is less than a long length array
              6. + *
              7. Check array contents element by element using {@link #append(Object, Object, Comparator)}
              8. + *
              + * + *

              This method will also will be called for the top level of multi-dimensional, + * ragged, and multi-typed arrays.

              + * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + */ + public CompareToBuilder append(final Object[] lhs, final Object[] rhs) { + return append(lhs, rhs, null); + } + + /** + *

              Appends to the builder the deep comparison of + * two Object arrays.

              + * + *
                + *
              1. Check if arrays are the same using ==
              2. + *
              3. Check if for null, null is less than non-null
              4. + *
              5. Check array length, a short length array is less than a long length array
              6. + *
              7. Check array contents element by element using {@link #append(Object, Object, Comparator)}
              8. + *
              + * + *

              This method will also will be called for the top level of multi-dimensional, + * ragged, and multi-typed arrays.

              + * + * @param lhs left-hand array + * @param rhs right-hand array + * @param comparator Comparator to use to compare the array elements, + * null means to treat lhs elements as Comparable. + * @return this - used to chain append calls + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + * @since 2.0 + */ + public CompareToBuilder append(final Object[] lhs, final Object[] rhs, final Comparator comparator) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null) { + comparison = -1; + return this; + } + if (rhs == null) { + comparison = +1; + return this; + } + if (lhs.length != rhs.length) { + comparison = (lhs.length < rhs.length) ? -1 : +1; + return this; + } + for (int i = 0; i < lhs.length && comparison == 0; i++) { + append(lhs[i], rhs[i], comparator); + } + return this; + } + + /** + *

              Appends to the builder the deep comparison of + * two long arrays.

              + * + *
                + *
              1. Check if arrays are the same using ==
              2. + *
              3. Check if for null, null is less than non-null
              4. + *
              5. Check array length, a shorter length array is less than a longer length array
              6. + *
              7. Check array contents element by element using {@link #append(long, long)}
              8. + *
              + * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(final long[] lhs, final long[] rhs) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null) { + comparison = -1; + return this; + } + if (rhs == null) { + comparison = +1; + return this; + } + if (lhs.length != rhs.length) { + comparison = (lhs.length < rhs.length) ? -1 : +1; + return this; + } + for (int i = 0; i < lhs.length && comparison == 0; i++) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Appends to the builder the deep comparison of + * two int arrays.

              + * + *
                + *
              1. Check if arrays are the same using ==
              2. + *
              3. Check if for null, null is less than non-null
              4. + *
              5. Check array length, a shorter length array is less than a longer length array
              6. + *
              7. Check array contents element by element using {@link #append(int, int)}
              8. + *
              + * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(final int[] lhs, final int[] rhs) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null) { + comparison = -1; + return this; + } + if (rhs == null) { + comparison = +1; + return this; + } + if (lhs.length != rhs.length) { + comparison = (lhs.length < rhs.length) ? -1 : +1; + return this; + } + for (int i = 0; i < lhs.length && comparison == 0; i++) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Appends to the builder the deep comparison of + * two short arrays.

              + * + *
                + *
              1. Check if arrays are the same using ==
              2. + *
              3. Check if for null, null is less than non-null
              4. + *
              5. Check array length, a shorter length array is less than a longer length array
              6. + *
              7. Check array contents element by element using {@link #append(short, short)}
              8. + *
              + * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(final short[] lhs, final short[] rhs) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null) { + comparison = -1; + return this; + } + if (rhs == null) { + comparison = +1; + return this; + } + if (lhs.length != rhs.length) { + comparison = (lhs.length < rhs.length) ? -1 : +1; + return this; + } + for (int i = 0; i < lhs.length && comparison == 0; i++) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Appends to the builder the deep comparison of + * two char arrays.

              + * + *
                + *
              1. Check if arrays are the same using ==
              2. + *
              3. Check if for null, null is less than non-null
              4. + *
              5. Check array length, a shorter length array is less than a longer length array
              6. + *
              7. Check array contents element by element using {@link #append(char, char)}
              8. + *
              + * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(final char[] lhs, final char[] rhs) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null) { + comparison = -1; + return this; + } + if (rhs == null) { + comparison = +1; + return this; + } + if (lhs.length != rhs.length) { + comparison = (lhs.length < rhs.length) ? -1 : +1; + return this; + } + for (int i = 0; i < lhs.length && comparison == 0; i++) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Appends to the builder the deep comparison of + * two byte arrays.

              + * + *
                + *
              1. Check if arrays are the same using ==
              2. + *
              3. Check if for null, null is less than non-null
              4. + *
              5. Check array length, a shorter length array is less than a longer length array
              6. + *
              7. Check array contents element by element using {@link #append(byte, byte)}
              8. + *
              + * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(final byte[] lhs, final byte[] rhs) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null) { + comparison = -1; + return this; + } + if (rhs == null) { + comparison = +1; + return this; + } + if (lhs.length != rhs.length) { + comparison = (lhs.length < rhs.length) ? -1 : +1; + return this; + } + for (int i = 0; i < lhs.length && comparison == 0; i++) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Appends to the builder the deep comparison of + * two double arrays.

              + * + *
                + *
              1. Check if arrays are the same using ==
              2. + *
              3. Check if for null, null is less than non-null
              4. + *
              5. Check array length, a shorter length array is less than a longer length array
              6. + *
              7. Check array contents element by element using {@link #append(double, double)}
              8. + *
              + * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(final double[] lhs, final double[] rhs) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null) { + comparison = -1; + return this; + } + if (rhs == null) { + comparison = +1; + return this; + } + if (lhs.length != rhs.length) { + comparison = (lhs.length < rhs.length) ? -1 : +1; + return this; + } + for (int i = 0; i < lhs.length && comparison == 0; i++) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Appends to the builder the deep comparison of + * two float arrays.

              + * + *
                + *
              1. Check if arrays are the same using ==
              2. + *
              3. Check if for null, null is less than non-null
              4. + *
              5. Check array length, a shorter length array is less than a longer length array
              6. + *
              7. Check array contents element by element using {@link #append(float, float)}
              8. + *
              + * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(final float[] lhs, final float[] rhs) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null) { + comparison = -1; + return this; + } + if (rhs == null) { + comparison = +1; + return this; + } + if (lhs.length != rhs.length) { + comparison = (lhs.length < rhs.length) ? -1 : +1; + return this; + } + for (int i = 0; i < lhs.length && comparison == 0; i++) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Appends to the builder the deep comparison of + * two boolean arrays.

              + * + *
                + *
              1. Check if arrays are the same using ==
              2. + *
              3. Check if for null, null is less than non-null
              4. + *
              5. Check array length, a shorter length array is less than a longer length array
              6. + *
              7. Check array contents element by element using {@link #append(boolean, boolean)}
              8. + *
              + * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + */ + public CompareToBuilder append(final boolean[] lhs, final boolean[] rhs) { + if (comparison != 0) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null) { + comparison = -1; + return this; + } + if (rhs == null) { + comparison = +1; + return this; + } + if (lhs.length != rhs.length) { + comparison = (lhs.length < rhs.length) ? -1 : +1; + return this; + } + for (int i = 0; i < lhs.length && comparison == 0; i++) { + append(lhs[i], rhs[i]); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Returns a negative integer, a positive integer, or zero as + * the builder has judged the "left-hand" side + * as less than, greater than, or equal to the "right-hand" + * side. + * + * @return final comparison result + * @see #build() + */ + public int toComparison() { + return comparison; + } + + /** + * Returns a negative Integer, a positive Integer, or zero as + * the builder has judged the "left-hand" side + * as less than, greater than, or equal to the "right-hand" + * side. + * + * @return final comparison result as an Integer + * @see #toComparison() + * @since 3.0 + */ + @Override + public Integer build() { + return Integer.valueOf(toComparison()); + } +} + diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Diff.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Diff.java new file mode 100644 index 000000000..0db2d2f0e --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Diff.java @@ -0,0 +1,118 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +import java.lang.reflect.Type; + +import com.fr.third.org.apache.commons.lang3.reflect.TypeUtils; +import com.fr.third.org.apache.commons.lang3.ObjectUtils; +import com.fr.third.org.apache.commons.lang3.tuple.Pair; + +/** + *

              + * A {@code Diff} contains the differences between two {@link Diffable} class + * fields. + *

              + * + *

              + * Typically, {@code Diff}s are retrieved by using a {@link DiffBuilder} to + * produce a {@link DiffResult}, containing the differences between two objects. + *

              + * + * + * @param + * The type of object contained within this {@code Diff}. Differences + * between primitive objects are stored as their Object wrapper + * equivalent. + * @since 3.3 + * @version $Id: Diff.java 1606051 2014-06-27 12:22:17Z ggregory $ + */ +public abstract class Diff extends Pair { + + private static final long serialVersionUID = 1L; + + private final Type type; + private final String fieldName; + + /** + *

              + * Constructs a new {@code Diff} for the given field name. + *

              + * + * @param fieldName + * the name of the field + */ + protected Diff(final String fieldName) { + this.type = ObjectUtils.defaultIfNull( + TypeUtils.getTypeArguments(getClass(), Diff.class).get( + Diff.class.getTypeParameters()[0]), Object.class); + this.fieldName = fieldName; + } + + /** + *

              + * Returns the type of the field. + *

              + * + * @return the field type + */ + public final Type getType() { + return type; + } + + /** + *

              + * Returns the name of the field. + *

              + * + * @return the field name + */ + public final String getFieldName() { + return fieldName; + } + + /** + *

              + * Returns a {@code String} representation of the {@code Diff}, with the + * following format:

              + * + *
              +     * [fieldname: left-value, right-value]
              +     * 
              + * + * + * @return the string representation + */ + @Override + public final String toString() { + return String.format("[%s: %s, %s]", fieldName, getLeft(), getRight()); + } + + /** + *

              + * Throws {@code UnsupportedOperationException}. + *

              + * + * @param value + * ignored + * @return nothing + */ + @Override + public final T setValue(final T value) { + throw new UnsupportedOperationException("Cannot alter Diff object."); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/DiffBuilder.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/DiffBuilder.java new file mode 100644 index 000000000..cc9d4b792 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/DiffBuilder.java @@ -0,0 +1,958 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; + +/** + *

              + * Assists in implementing {@link Diffable#diff(Object)} methods. + *

              + * + *

              + * To use this class, write code as follows: + *

              + * + *
              + * public class Person implements Diffable<Person> {
              + *   String name;
              + *   int age;
              + *   boolean smoker;
              + *   
              + *   ...
              + *   
              + *   public DiffResult diff(Person obj) {
              + *     // No need for null check, as NullPointerException correct if obj is null
              + *     return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
              + *       .append("name", this.name, obj.name)
              + *       .append("age", this.age, obj.age)
              + *       .append("smoker", this.smoker, obj.smoker)
              + *       .build();
              + *   }
              + * }
              + * 
              + * + *

              + * The {@code ToStringStyle} passed to the constructor is embedded in the + * returned {@code DiffResult} and influences the style of the + * {@code DiffResult.toString()} method. This style choice can be overridden by + * calling {@link DiffResult#toString(ToStringStyle)}. + *

              + * + * @since 3.3 + * @version $Id: DiffBuilder.java 1669782 2015-03-28 15:05:04Z britter $ + * @see Diffable + * @see Diff + * @see DiffResult + * @see ToStringStyle + */ +public class DiffBuilder implements Builder { + + private final List> diffs; + private final boolean objectsTriviallyEqual; + private final Object left; + private final Object right; + private final ToStringStyle style; + + /** + *

              + * Constructs a builder for the specified objects with the specified style. + *

              + * + *

              + * If {@code lhs == rhs} or {@code lhs.equals(rhs)} then the builder will + * not evaluate any calls to {@code append(...)} and will return an empty + * {@link DiffResult} when {@link #build()} is executed. + *

              + * + * @param lhs + * {@code this} object + * @param rhs + * the object to diff against + * @param style + * the style will use when outputting the objects, {@code null} + * uses the default + * @param testTriviallyEqual + * If true, this will test if lhs and rhs are the same or equal. + * All of the append(fieldName, lhs, rhs) methods will abort + * without creating a field {@link Diff} if the trivially equal + * test is enabled and returns true. The result of this test + * is never changed throughout the life of this {@link DiffBuilder}. + * @throws IllegalArgumentException + * if {@code lhs} or {@code rhs} is {@code null} + * @since 3.4 + */ + public DiffBuilder(final Object lhs, final Object rhs, + final ToStringStyle style, final boolean testTriviallyEqual) { + + if (lhs == null) { + throw new IllegalArgumentException("lhs cannot be null"); + } + if (rhs == null) { + throw new IllegalArgumentException("rhs cannot be null"); + } + + this.diffs = new ArrayList>(); + this.left = lhs; + this.right = rhs; + this.style = style; + + // Don't compare any fields if objects equal + this.objectsTriviallyEqual = testTriviallyEqual && (lhs == rhs || lhs.equals(rhs)); + } + + /** + *

              + * Constructs a builder for the specified objects with the specified style. + *

              + * + *

              + * If {@code lhs == rhs} or {@code lhs.equals(rhs)} then the builder will + * not evaluate any calls to {@code append(...)} and will return an empty + * {@link DiffResult} when {@link #build()} is executed. + *

              + * + *

              + * This delegates to {@link #DiffBuilder(Object, Object, ToStringStyle, boolean)} + * with the testTriviallyEqual flag enabled. + *

              + * + * @param lhs + * {@code this} object + * @param rhs + * the object to diff against + * @param style + * the style will use when outputting the objects, {@code null} + * uses the default + * @throws IllegalArgumentException + * if {@code lhs} or {@code rhs} is {@code null} + */ + public DiffBuilder(final Object lhs, final Object rhs, + final ToStringStyle style) { + + this(lhs, rhs, style, true); + } + + /** + *

              + * Test if two {@code boolean}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code boolean} + * @param rhs + * the right hand {@code boolean} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final boolean lhs, + final boolean rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Boolean getLeft() { + return Boolean.valueOf(lhs); + } + + @Override + public Boolean getRight() { + return Boolean.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code boolean[]}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code boolean[]} + * @param rhs + * the right hand {@code boolean[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final boolean[] lhs, + final boolean[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Boolean[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Boolean[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code byte}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code byte} + * @param rhs + * the right hand {@code byte} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final byte lhs, + final byte rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Byte getLeft() { + return Byte.valueOf(lhs); + } + + @Override + public Byte getRight() { + return Byte.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code byte[]}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code byte[]} + * @param rhs + * the right hand {@code byte[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final byte[] lhs, + final byte[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Byte[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Byte[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code char}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code char} + * @param rhs + * the right hand {@code char} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final char lhs, + final char rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Character getLeft() { + return Character.valueOf(lhs); + } + + @Override + public Character getRight() { + return Character.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code char[]}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code char[]} + * @param rhs + * the right hand {@code char[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final char[] lhs, + final char[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Character[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Character[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code double}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code double} + * @param rhs + * the right hand {@code double} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final double lhs, + final double rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (Double.doubleToLongBits(lhs) != Double.doubleToLongBits(rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Double getLeft() { + return Double.valueOf(lhs); + } + + @Override + public Double getRight() { + return Double.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code double[]}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code double[]} + * @param rhs + * the right hand {@code double[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final double[] lhs, + final double[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Double[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Double[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code float}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code float} + * @param rhs + * the right hand {@code float} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final float lhs, + final float rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (Float.floatToIntBits(lhs) != Float.floatToIntBits(rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Float getLeft() { + return Float.valueOf(lhs); + } + + @Override + public Float getRight() { + return Float.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code float[]}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code float[]} + * @param rhs + * the right hand {@code float[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final float[] lhs, + final float[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Float[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Float[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code int}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code int} + * @param rhs + * the right hand {@code int} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final int lhs, + final int rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Integer getLeft() { + return Integer.valueOf(lhs); + } + + @Override + public Integer getRight() { + return Integer.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code int[]}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code int[]} + * @param rhs + * the right hand {@code int[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final int[] lhs, + final int[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Integer[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Integer[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code long}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code long} + * @param rhs + * the right hand {@code long} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final long lhs, + final long rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Long getLeft() { + return Long.valueOf(lhs); + } + + @Override + public Long getRight() { + return Long.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code long[]}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code long[]} + * @param rhs + * the right hand {@code long[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final long[] lhs, + final long[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Long[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Long[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code short}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code short} + * @param rhs + * the right hand {@code short} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final short lhs, + final short rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Short getLeft() { + return Short.valueOf(lhs); + } + + @Override + public Short getRight() { + return Short.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code short[]}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code short[]} + * @param rhs + * the right hand {@code short[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final short[] lhs, + final short[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Short[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Short[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

              + * Test if two {@code Objects}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code Object} + * @param rhs + * the right hand {@code Object} + * @return this + */ + public DiffBuilder append(final String fieldName, final Object lhs, + final Object rhs) { + + if (objectsTriviallyEqual) { + return this; + } + if (lhs == rhs) { + return this; + } + + Object objectToTest; + if (lhs != null) { + objectToTest = lhs; + } else { + // rhs cannot be null, as lhs != rhs + objectToTest = rhs; + } + + if (objectToTest.getClass().isArray()) { + if (objectToTest instanceof boolean[]) { + return append(fieldName, (boolean[]) lhs, (boolean[]) rhs); + } + if (objectToTest instanceof byte[]) { + return append(fieldName, (byte[]) lhs, (byte[]) rhs); + } + if (objectToTest instanceof char[]) { + return append(fieldName, (char[]) lhs, (char[]) rhs); + } + if (objectToTest instanceof double[]) { + return append(fieldName, (double[]) lhs, (double[]) rhs); + } + if (objectToTest instanceof float[]) { + return append(fieldName, (float[]) lhs, (float[]) rhs); + } + if (objectToTest instanceof int[]) { + return append(fieldName, (int[]) lhs, (int[]) rhs); + } + if (objectToTest instanceof long[]) { + return append(fieldName, (long[]) lhs, (long[]) rhs); + } + if (objectToTest instanceof short[]) { + return append(fieldName, (short[]) lhs, (short[]) rhs); + } + + return append(fieldName, (Object[]) lhs, (Object[]) rhs); + } + + // Not array type + if (lhs != null && lhs.equals(rhs)) { + return this; + } + + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Object getLeft() { + return lhs; + } + + @Override + public Object getRight() { + return rhs; + } + }); + + return this; + } + + /** + *

              + * Test if two {@code Object[]}s are equal. + *

              + * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code Object[]} + * @param rhs + * the right hand {@code Object[]} + * @return this + */ + public DiffBuilder append(final String fieldName, final Object[] lhs, + final Object[] rhs) { + if (objectsTriviallyEqual) { + return this; + } + + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Object[] getLeft() { + return lhs; + } + + @Override + public Object[] getRight() { + return rhs; + } + }); + } + + return this; + } + + /** + *

              + * Builds a {@link DiffResult} based on the differences appended to this + * builder. + *

              + * + * @return a {@code DiffResult} containing the differences between the two + * objects. + */ + @Override + public DiffResult build() { + return new DiffResult(left, right, diffs, style); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/DiffResult.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/DiffResult.java new file mode 100644 index 000000000..0072671ad --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/DiffResult.java @@ -0,0 +1,208 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + *

              + * A {@code DiffResult} contains a collection of the differences between two + * {@link Diffable} objects. Typically these differences are displayed using + * {@link #toString()} method, which returns a string describing the fields that + * differ between the objects. + *

              + *

              + * Use a {@link DiffBuilder} to build a {@code DiffResult} comparing two objects. + *

              + * + * @since 3.3 + * @version $Id: DiffResult.java 1606051 2014-06-27 12:22:17Z ggregory $ + */ +public class DiffResult implements Iterable> { + + /** + *

              + * The {@code String} returned when the objects have no differences: + * {@value} + *

              + */ + public static final String OBJECTS_SAME_STRING = ""; + + private static final String DIFFERS_STRING = "differs from"; + + private final List> diffs; + private final Object lhs; + private final Object rhs; + private final ToStringStyle style; + + /** + *

              + * Creates a {@link DiffResult} containing the differences between two + * objects. + *

              + * + * @param lhs + * the left hand object + * @param rhs + * the right hand object + * @param diffs + * the list of differences, may be empty + * @param style + * the style to use for the {@link #toString()} method. May be + * {@code null}, in which case + * {@link ToStringStyle#DEFAULT_STYLE} is used + * @throws IllegalArgumentException + * if {@code lhs}, {@code rhs} or {@code diffs} is {@code null} + */ + DiffResult(final Object lhs, final Object rhs, final List> diffs, + final ToStringStyle style) { + if (lhs == null) { + throw new IllegalArgumentException( + "Left hand object cannot be null"); + } + if (rhs == null) { + throw new IllegalArgumentException( + "Right hand object cannot be null"); + } + if (diffs == null) { + throw new IllegalArgumentException( + "List of differences cannot be null"); + } + + this.diffs = diffs; + this.lhs = lhs; + this.rhs = rhs; + + if (style == null) { + this.style = ToStringStyle.DEFAULT_STYLE; + } else { + this.style = style; + } + } + + /** + *

              + * Returns an unmodifiable list of {@code Diff}s. The list may be empty if + * there were no differences between the objects. + *

              + * + * @return an unmodifiable list of {@code Diff}s + */ + public List> getDiffs() { + return Collections.unmodifiableList(diffs); + } + + /** + *

              + * Returns the number of differences between the two objects. + *

              + * + * @return the number of differences + */ + public int getNumberOfDiffs() { + return diffs.size(); + } + + /** + *

              + * Returns the style used by the {@link #toString()} method. + *

              + * + * @return the style + */ + public ToStringStyle getToStringStyle() { + return style; + } + + /** + *

              + * Builds a {@code String} description of the differences contained within + * this {@code DiffResult}. A {@link ToStringBuilder} is used for each object + * and the style of the output is governed by the {@code ToStringStyle} + * passed to the constructor. + *

              + * + *

              + * If there are no differences stored in this list, the method will return + * {@link #OBJECTS_SAME_STRING}. Otherwise, using the example given in + * {@link Diffable} and {@link ToStringStyle#SHORT_PREFIX_STYLE}, an output + * might be: + *

              + * + *
              +     * Person[name=John Doe,age=32] differs from Person[name=Joe Bloggs,age=26]
              +     * 
              + * + *

              + * This indicates that the objects differ in name and age, but not in + * smoking status. + *

              + * + *

              + * To use a different {@code ToStringStyle} for an instance of this class, + * use {@link #toString(ToStringStyle)}. + *

              + * + * @return a {@code String} description of the differences. + */ + @Override + public String toString() { + return toString(style); + } + + /** + *

              + * Builds a {@code String} description of the differences contained within + * this {@code DiffResult}, using the supplied {@code ToStringStyle}. + *

              + * + * @param style + * the {@code ToStringStyle} to use when outputting the objects + * + * @return a {@code String} description of the differences. + */ + public String toString(final ToStringStyle style) { + if (diffs.size() == 0) { + return OBJECTS_SAME_STRING; + } + + final ToStringBuilder lhsBuilder = new ToStringBuilder(lhs, style); + final ToStringBuilder rhsBuilder = new ToStringBuilder(rhs, style); + + for (final Diff diff : diffs) { + lhsBuilder.append(diff.getFieldName(), diff.getLeft()); + rhsBuilder.append(diff.getFieldName(), diff.getRight()); + } + + return String.format("%s %s %s", lhsBuilder.build(), DIFFERS_STRING, + rhsBuilder.build()); + } + + /** + *

              + * Returns an iterator over the {@code Diff} objects contained in this list. + *

              + * + * @return the iterator + */ + @Override + public Iterator> iterator() { + return diffs.iterator(); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Diffable.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Diffable.java new file mode 100644 index 000000000..d96310b1c --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/Diffable.java @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +/** + *

              {@code Diffable} classes can be compared with other objects + * for differences. The {@link DiffResult} object retrieved can be queried + * for a list of differences or printed using the {@link DiffResult#toString()}.

              + * + *

              The calculation of the differences is consistent with equals if + * and only if {@code d1.equals(d2)} implies {@code d1.diff(d2) == ""}. + * It is strongly recommended that implementations are consistent with equals + * to avoid confusion. Note that {@code null} is not an instance of any class + * and {@code d1.diff(null)} should throw a {@code NullPointerException}.

              + * + *

              + * {@code Diffable} classes lend themselves well to unit testing, in which a + * easily readable description of the differences between an anticipated result and + * an actual result can be retrieved. For example: + *

              + *
              + * Assert.assertEquals(expected.diff(result), expected, result);
              + * 
              + * + * @param the type of objects that this object may be differentiated against + * @since 3.3 + * @version $Id: Diffable.java 1561225 2014-01-24 23:17:29Z djones $ + */ +public interface Diffable { + + /** + *

              Retrieves a list of the differences between + * this object and the supplied object.

              + * + * @param obj the object to diff against, can be {@code null} + * @return a list of differences + * @throws NullPointerException if the specified object is {@code null} + */ + DiffResult diff(T obj); +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/EqualsBuilder.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/EqualsBuilder.java new file mode 100644 index 000000000..8a17939cd --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/EqualsBuilder.java @@ -0,0 +1,954 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; +import com.fr.third.org.apache.commons.lang3.tuple.Pair; + +/** + *

              Assists in implementing {@link Object#equals(Object)} methods.

              + * + *

              This class provides methods to build a good equals method for any + * class. It follows rules laid out in + * Effective Java + * , by Joshua Bloch. In particular the rule for comparing doubles, + * floats, and arrays can be tricky. Also, making sure that + * equals() and hashCode() are consistent can be + * difficult.

              + * + *

              Two Objects that compare as equals must generate the same hash code, + * but two Objects with the same hash code do not have to be equal.

              + * + *

              All relevant fields should be included in the calculation of equals. + * Derived fields may be ignored. In particular, any field used in + * generating a hash code must be used in the equals method, and vice + * versa.

              + * + *

              Typical use for the code is as follows:

              + *
              + * public boolean equals(Object obj) {
              + *   if (obj == null) { return false; }
              + *   if (obj == this) { return true; }
              + *   if (obj.getClass() != getClass()) {
              + *     return false;
              + *   }
              + *   MyClass rhs = (MyClass) obj;
              + *   return new EqualsBuilder()
              + *                 .appendSuper(super.equals(obj))
              + *                 .append(field1, rhs.field1)
              + *                 .append(field2, rhs.field2)
              + *                 .append(field3, rhs.field3)
              + *                 .isEquals();
              + *  }
              + * 
              + * + *

              Alternatively, there is a method that uses reflection to determine + * the fields to test. Because these fields are usually private, the method, + * reflectionEquals, uses AccessibleObject.setAccessible to + * change the visibility of the fields. This will fail under a security + * manager, unless the appropriate permissions are set up correctly. It is + * also slower than testing explicitly. Non-primitive fields are compared using + * equals().

              + * + *

              A typical invocation for this method would look like:

              + *
              + * public boolean equals(Object obj) {
              + *   return EqualsBuilder.reflectionEquals(this, obj);
              + * }
              + * 
              + * + * @since 1.0 + * @version $Id: EqualsBuilder.java 1623970 2014-09-10 11:32:53Z djones $ + */ +public class EqualsBuilder implements Builder { + + /** + *

              + * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops. + *

              + * + * @since 3.0 + */ + private static final ThreadLocal>> REGISTRY = new ThreadLocal>>(); + + /* + * NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode() + * we are in the process of calculating. + * + * So we generate a one-to-one mapping from the original object to a new object. + * + * Now HashSet uses equals() to determine if two elements with the same hashcode really + * are equal, so we also need to ensure that the replacement objects are only equal + * if the original objects are identical. + * + * The original implementation (2.4 and before) used the System.indentityHashCode() + * method - however this is not guaranteed to generate unique ids (e.g. LANG-459) + * + * We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey) + * to disambiguate the duplicate ids. + */ + + /** + *

              + * Returns the registry of object pairs being traversed by the reflection + * methods in the current thread. + *

              + * + * @return Set the registry of objects being traversed + * @since 3.0 + */ + static Set> getRegistry() { + return REGISTRY.get(); + } + + /** + *

              + * Converters value pair into a register pair. + *

              + * + * @param lhs this object + * @param rhs the other object + * + * @return the pair + */ + static Pair getRegisterPair(final Object lhs, final Object rhs) { + final IDKey left = new IDKey(lhs); + final IDKey right = new IDKey(rhs); + return Pair.of(left, right); + } + + /** + *

              + * Returns true if the registry contains the given object pair. + * Used by the reflection methods to avoid infinite loops. + * Objects might be swapped therefore a check is needed if the object pair + * is registered in given or swapped order. + *

              + * + * @param lhs this object to lookup in registry + * @param rhs the other object to lookup on registry + * @return boolean true if the registry contains the given object. + * @since 3.0 + */ + static boolean isRegistered(final Object lhs, final Object rhs) { + final Set> registry = getRegistry(); + final Pair pair = getRegisterPair(lhs, rhs); + final Pair swappedPair = Pair.of(pair.getLeft(), pair.getRight()); + + return registry != null + && (registry.contains(pair) || registry.contains(swappedPair)); + } + + /** + *

              + * Registers the given object pair. + * Used by the reflection methods to avoid infinite loops. + *

              + * + * @param lhs this object to register + * @param rhs the other object to register + */ + static void register(final Object lhs, final Object rhs) { + synchronized (EqualsBuilder.class) { + if (getRegistry() == null) { + REGISTRY.set(new HashSet>()); + } + } + + final Set> registry = getRegistry(); + final Pair pair = getRegisterPair(lhs, rhs); + registry.add(pair); + } + + /** + *

              + * Unregisters the given object pair. + *

              + * + *

              + * Used by the reflection methods to avoid infinite loops. + * + * @param lhs this object to unregister + * @param rhs the other object to unregister + * @since 3.0 + */ + static void unregister(final Object lhs, final Object rhs) { + Set> registry = getRegistry(); + if (registry != null) { + final Pair pair = getRegisterPair(lhs, rhs); + registry.remove(pair); + synchronized (EqualsBuilder.class) { + //read again + registry = getRegistry(); + if (registry != null && registry.isEmpty()) { + REGISTRY.remove(); + } + } + } + } + + /** + * If the fields tested are equals. + * The default value is true. + */ + private boolean isEquals = true; + + /** + *

              Constructor for EqualsBuilder.

              + * + *

              Starts off assuming that equals is true.

              + * @see Object#equals(Object) + */ + public EqualsBuilder() { + // do nothing for now. + } + + //------------------------------------------------------------------------- + + /** + *

              This method uses reflection to determine if the two Objects + * are equal.

              + * + *

              It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * equals().

              + * + *

              Transient members will be not be tested, as they are likely derived + * fields, and not part of the value of the Object.

              + * + *

              Static fields will not be tested. Superclass fields will be included.

              + * + * @param lhs this object + * @param rhs the other object + * @param excludeFields Collection of String field names to exclude from testing + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { + return reflectionEquals(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); + } + + /** + *

              This method uses reflection to determine if the two Objects + * are equal.

              + * + *

              It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * equals().

              + * + *

              Transient members will be not be tested, as they are likely derived + * fields, and not part of the value of the Object.

              + * + *

              Static fields will not be tested. Superclass fields will be included.

              + * + * @param lhs this object + * @param rhs the other object + * @param excludeFields array of field names to exclude from testing + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { + return reflectionEquals(lhs, rhs, false, null, excludeFields); + } + + /** + *

              This method uses reflection to determine if the two Objects + * are equal.

              + * + *

              It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * equals().

              + * + *

              If the TestTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

              + * + *

              Static fields will not be tested. Superclass fields will be included.

              + * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) { + return reflectionEquals(lhs, rhs, testTransients, null); + } + + /** + *

              This method uses reflection to determine if the two Objects + * are equal.

              + * + *

              It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * equals().

              + * + *

              If the testTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

              + * + *

              Static fields will not be included. Superclass fields will be appended + * up to and including the specified superclass. A null superclass is treated + * as java.lang.Object.

              + * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @param reflectUpToClass the superclass to reflect up to (inclusive), + * may be null + * @param excludeFields array of field names to exclude from testing + * @return true if the two Objects have tested equals. + * @since 2.0 + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, + final String... excludeFields) { + if (lhs == rhs) { + return true; + } + if (lhs == null || rhs == null) { + return false; + } + // Find the leaf class since there may be transients in the leaf + // class or in classes between the leaf and root. + // If we are not testing transients or a subclass has no ivars, + // then a subclass can test equals to a superclass. + final Class lhsClass = lhs.getClass(); + final Class rhsClass = rhs.getClass(); + Class testClass; + if (lhsClass.isInstance(rhs)) { + testClass = lhsClass; + if (!rhsClass.isInstance(lhs)) { + // rhsClass is a subclass of lhsClass + testClass = rhsClass; + } + } else if (rhsClass.isInstance(lhs)) { + testClass = rhsClass; + if (!lhsClass.isInstance(rhs)) { + // lhsClass is a subclass of rhsClass + testClass = lhsClass; + } + } else { + // The two classes are not related. + return false; + } + final EqualsBuilder equalsBuilder = new EqualsBuilder(); + try { + if (testClass.isArray()) { + equalsBuilder.append(lhs, rhs); + } else { + reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); + while (testClass.getSuperclass() != null && testClass != reflectUpToClass) { + testClass = testClass.getSuperclass(); + reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); + } + } + } catch (final IllegalArgumentException e) { + // In this case, we tried to test a subclass vs. a superclass and + // the subclass has ivars or the ivars are transient and + // we are testing transients. + // If a subclass has ivars that we are trying to test them, we get an + // exception and we know that the objects are not equal. + return false; + } + return equalsBuilder.isEquals(); + } + + /** + *

              Appends the fields and values defined by the given object of the + * given Class.

              + * + * @param lhs the left hand object + * @param rhs the right hand object + * @param clazz the class to append details of + * @param builder the builder to append to + * @param useTransients whether to test transient fields + * @param excludeFields array of field names to exclude from testing + */ + private static void reflectionAppend( + final Object lhs, + final Object rhs, + final Class clazz, + final EqualsBuilder builder, + final boolean useTransients, + final String[] excludeFields) { + + if (isRegistered(lhs, rhs)) { + return; + } + + try { + register(lhs, rhs); + final Field[] fields = clazz.getDeclaredFields(); + AccessibleObject.setAccessible(fields, true); + for (int i = 0; i < fields.length && builder.isEquals; i++) { + final Field f = fields[i]; + if (!ArrayUtils.contains(excludeFields, f.getName()) + && (f.getName().indexOf('$') == -1) + && (useTransients || !Modifier.isTransient(f.getModifiers())) + && (!Modifier.isStatic(f.getModifiers()))) { + try { + builder.append(f.get(lhs), f.get(rhs)); + } catch (final IllegalAccessException e) { + //this can't happen. Would get a Security exception instead + //throw a runtime exception in case the impossible happens. + throw new InternalError("Unexpected IllegalAccessException"); + } + } + } + } finally { + unregister(lhs, rhs); + } + } + + //------------------------------------------------------------------------- + + /** + *

              Adds the result of super.equals() to this builder.

              + * + * @param superEquals the result of calling super.equals() + * @return EqualsBuilder - used to chain calls. + * @since 2.0 + */ + public EqualsBuilder appendSuper(final boolean superEquals) { + if (isEquals == false) { + return this; + } + isEquals = superEquals; + return this; + } + + //------------------------------------------------------------------------- + + /** + *

              Test if two Objects are equal using their + * equals method.

              + * + * @param lhs the left hand object + * @param rhs the right hand object + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final Object lhs, final Object rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + final Class lhsClass = lhs.getClass(); + if (!lhsClass.isArray()) { + // The simple case, not an array, just test the element + isEquals = lhs.equals(rhs); + } else if (lhs.getClass() != rhs.getClass()) { + // Here when we compare different dimensions, for example: a boolean[][] to a boolean[] + this.setEquals(false); + } + // 'Switch' on type of array, to dispatch to the correct handler + // This handles multi dimensional arrays of the same depth + else if (lhs instanceof long[]) { + append((long[]) lhs, (long[]) rhs); + } else if (lhs instanceof int[]) { + append((int[]) lhs, (int[]) rhs); + } else if (lhs instanceof short[]) { + append((short[]) lhs, (short[]) rhs); + } else if (lhs instanceof char[]) { + append((char[]) lhs, (char[]) rhs); + } else if (lhs instanceof byte[]) { + append((byte[]) lhs, (byte[]) rhs); + } else if (lhs instanceof double[]) { + append((double[]) lhs, (double[]) rhs); + } else if (lhs instanceof float[]) { + append((float[]) lhs, (float[]) rhs); + } else if (lhs instanceof boolean[]) { + append((boolean[]) lhs, (boolean[]) rhs); + } else { + // Not an array of primitives + append((Object[]) lhs, (Object[]) rhs); + } + return this; + } + + /** + *

              + * Test if two long s are equal. + *

              + * + * @param lhs + * the left hand long + * @param rhs + * the right hand long + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final long lhs, final long rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

              Test if two ints are equal.

              + * + * @param lhs the left hand int + * @param rhs the right hand int + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final int lhs, final int rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

              Test if two shorts are equal.

              + * + * @param lhs the left hand short + * @param rhs the right hand short + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final short lhs, final short rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

              Test if two chars are equal.

              + * + * @param lhs the left hand char + * @param rhs the right hand char + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final char lhs, final char rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

              Test if two bytes are equal.

              + * + * @param lhs the left hand byte + * @param rhs the right hand byte + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final byte lhs, final byte rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

              Test if two doubles are equal by testing that the + * pattern of bits returned by doubleToLong are equal.

              + * + *

              This handles NaNs, Infinities, and -0.0.

              + * + *

              It is compatible with the hash code generated by + * HashCodeBuilder.

              + * + * @param lhs the left hand double + * @param rhs the right hand double + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final double lhs, final double rhs) { + if (isEquals == false) { + return this; + } + return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); + } + + /** + *

              Test if two floats are equal byt testing that the + * pattern of bits returned by doubleToLong are equal.

              + * + *

              This handles NaNs, Infinities, and -0.0.

              + * + *

              It is compatible with the hash code generated by + * HashCodeBuilder.

              + * + * @param lhs the left hand float + * @param rhs the right hand float + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final float lhs, final float rhs) { + if (isEquals == false) { + return this; + } + return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); + } + + /** + *

              Test if two booleanss are equal.

              + * + * @param lhs the left hand boolean + * @param rhs the right hand boolean + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final boolean lhs, final boolean rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

              Performs a deep comparison of two Object arrays.

              + * + *

              This also will be called for the top level of + * multi-dimensional, ragged, and multi-typed arrays.

              + * + * @param lhs the left hand Object[] + * @param rhs the right hand Object[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final Object[] lhs, final Object[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Deep comparison of array of long. Length and all + * values are compared.

              + * + *

              The method {@link #append(long, long)} is used.

              + * + * @param lhs the left hand long[] + * @param rhs the right hand long[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final long[] lhs, final long[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Deep comparison of array of int. Length and all + * values are compared.

              + * + *

              The method {@link #append(int, int)} is used.

              + * + * @param lhs the left hand int[] + * @param rhs the right hand int[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final int[] lhs, final int[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Deep comparison of array of short. Length and all + * values are compared.

              + * + *

              The method {@link #append(short, short)} is used.

              + * + * @param lhs the left hand short[] + * @param rhs the right hand short[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final short[] lhs, final short[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Deep comparison of array of char. Length and all + * values are compared.

              + * + *

              The method {@link #append(char, char)} is used.

              + * + * @param lhs the left hand char[] + * @param rhs the right hand char[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final char[] lhs, final char[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Deep comparison of array of byte. Length and all + * values are compared.

              + * + *

              The method {@link #append(byte, byte)} is used.

              + * + * @param lhs the left hand byte[] + * @param rhs the right hand byte[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final byte[] lhs, final byte[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Deep comparison of array of double. Length and all + * values are compared.

              + * + *

              The method {@link #append(double, double)} is used.

              + * + * @param lhs the left hand double[] + * @param rhs the right hand double[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final double[] lhs, final double[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Deep comparison of array of float. Length and all + * values are compared.

              + * + *

              The method {@link #append(float, float)} is used.

              + * + * @param lhs the left hand float[] + * @param rhs the right hand float[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final float[] lhs, final float[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Deep comparison of array of boolean. Length and all + * values are compared.

              + * + *

              The method {@link #append(boolean, boolean)} is used.

              + * + * @param lhs the left hand boolean[] + * @param rhs the right hand boolean[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final boolean[] lhs, final boolean[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

              Returns true if the fields that have been checked + * are all equal.

              + * + * @return boolean + */ + public boolean isEquals() { + return this.isEquals; + } + + /** + *

              Returns true if the fields that have been checked + * are all equal.

              + * + * @return true if all of the fields that have been checked + * are equal, false otherwise. + * + * @since 3.0 + */ + @Override + public Boolean build() { + return Boolean.valueOf(isEquals()); + } + + /** + * Sets the isEquals value. + * + * @param isEquals The value to set. + * @since 2.1 + */ + protected void setEquals(final boolean isEquals) { + this.isEquals = isEquals; + } + + /** + * Reset the EqualsBuilder so you can use the same object again + * @since 2.5 + */ + public void reset() { + this.isEquals = true; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/HashCodeBuilder.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/HashCodeBuilder.java new file mode 100644 index 000000000..a5ed0af2c --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/HashCodeBuilder.java @@ -0,0 +1,971 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.lang3.builder; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; +import com.fr.third.org.apache.commons.lang3.Validate; + +/** + *

              + * Assists in implementing {@link Object#hashCode()} methods. + *

              + * + *

              + * This class enables a good hashCode method to be built for any class. It follows the rules laid out in + * the book Effective Java by Joshua Bloch. Writing a + * good hashCode method is actually quite difficult. This class aims to simplify the process. + *

              + * + *

              + * The following is the approach taken. When appending a data field, the current total is multiplied by the + * multiplier then a relevant value + * for that data type is added. For example, if the current hashCode is 17, and the multiplier is 37, then + * appending the integer 45 will create a hashcode of 674, namely 17 * 37 + 45. + *

              + * + *

              + * All relevant fields from the object should be included in the hashCode method. Derived fields may be + * excluded. In general, any field used in the equals method must be used in the hashCode + * method. + *

              + * + *

              + * To use this class write code as follows: + *

              + * + *
              + * public class Person {
              + *   String name;
              + *   int age;
              + *   boolean smoker;
              + *   ...
              + *
              + *   public int hashCode() {
              + *     // you pick a hard-coded, randomly chosen, non-zero, odd number
              + *     // ideally different for each class
              + *     return new HashCodeBuilder(17, 37).
              + *       append(name).
              + *       append(age).
              + *       append(smoker).
              + *       toHashCode();
              + *   }
              + * }
              + * 
              + * + *

              + * If required, the superclass hashCode() can be added using {@link #appendSuper}. + *

              + * + *

              + * Alternatively, there is a method that uses reflection to determine the fields to test. Because these fields are + * usually private, the method, reflectionHashCode, uses AccessibleObject.setAccessible + * to change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions + * are set up correctly. It is also slower than testing explicitly. + *

              + * + *

              + * A typical invocation for this method would look like: + *

              + * + *
              + * public int hashCode() {
              + *   return HashCodeBuilder.reflectionHashCode(this);
              + * }
              + * 
              + * + * @since 1.0 + * @version $Id: HashCodeBuilder.java 1627889 2014-09-26 21:19:12Z djones $ + */ +public class HashCodeBuilder implements Builder { + /** + * The default initial value to use in reflection hash code building. + */ + private static final int DEFAULT_INITIAL_VALUE = 17; + + /** + * The default multipler value to use in reflection hash code building. + */ + private static final int DEFAULT_MULTIPLIER_VALUE = 37; + + /** + *

              + * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops. + *

              + * + * @since 2.3 + */ + private static final ThreadLocal> REGISTRY = new ThreadLocal>(); + + /* + * NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode() + * we are in the process of calculating. + * + * So we generate a one-to-one mapping from the original object to a new object. + * + * Now HashSet uses equals() to determine if two elements with the same hashcode really + * are equal, so we also need to ensure that the replacement objects are only equal + * if the original objects are identical. + * + * The original implementation (2.4 and before) used the System.indentityHashCode() + * method - however this is not guaranteed to generate unique ids (e.g. LANG-459) + * + * We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey) + * to disambiguate the duplicate ids. + */ + + /** + *

              + * Returns the registry of objects being traversed by the reflection methods in the current thread. + *

              + * + * @return Set the registry of objects being traversed + * @since 2.3 + */ + static Set getRegistry() { + return REGISTRY.get(); + } + + /** + *

              + * Returns true if the registry contains the given object. Used by the reflection methods to avoid + * infinite loops. + *

              + * + * @param value + * The object to lookup in the registry. + * @return boolean true if the registry contains the given object. + * @since 2.3 + */ + static boolean isRegistered(final Object value) { + final Set registry = getRegistry(); + return registry != null && registry.contains(new IDKey(value)); + } + + /** + *

              + * Appends the fields and values defined by the given object of the given Class. + *

              + * + * @param object + * the object to append details of + * @param clazz + * the class to append details of + * @param builder + * the builder to append to + * @param useTransients + * whether to use transient fields + * @param excludeFields + * Collection of String field names to exclude from use in calculation of hash code + */ + private static void reflectionAppend(final Object object, final Class clazz, final HashCodeBuilder builder, final boolean useTransients, + final String[] excludeFields) { + if (isRegistered(object)) { + return; + } + try { + register(object); + final Field[] fields = clazz.getDeclaredFields(); + AccessibleObject.setAccessible(fields, true); + for (final Field field : fields) { + if (!ArrayUtils.contains(excludeFields, field.getName()) + && (field.getName().indexOf('$') == -1) + && (useTransients || !Modifier.isTransient(field.getModifiers())) + && (!Modifier.isStatic(field.getModifiers()))) { + try { + final Object fieldValue = field.get(object); + builder.append(fieldValue); + } catch (final IllegalAccessException e) { + // this can't happen. Would get a Security exception instead + // throw a runtime exception in case the impossible happens. + throw new InternalError("Unexpected IllegalAccessException"); + } + } + } + } finally { + unregister(object); + } + } + + /** + *

              + * Uses reflection to build a valid hash code from the fields of {@code object}. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * Transient members will be not be used, as they are likely derived fields, and not part of the value of the + * Object. + *

              + * + *

              + * Static fields will not be tested. Superclass fields will be included. + *

              + * + *

              + * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class, + * however this is not vital. Prime numbers are preferred, especially for the multiplier. + *

              + * + * @param initialNonZeroOddNumber + * a non-zero, odd number used as the initial value. This will be the returned + * value if no fields are found to include in the hash code + * @param multiplierNonZeroOddNumber + * a non-zero, odd number used as the multiplier + * @param object + * the Object to create a hashCode for + * @return int hash code + * @throws IllegalArgumentException + * if the Object is null + * @throws IllegalArgumentException + * if the number is zero or even + */ + public static int reflectionHashCode(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final Object object) { + return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, false, null); + } + + /** + *

              + * Uses reflection to build a valid hash code from the fields of {@code object}. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * If the TestTransients parameter is set to true, transient members will be tested, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

              + * + *

              + * Static fields will not be tested. Superclass fields will be included. + *

              + * + *

              + * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class, + * however this is not vital. Prime numbers are preferred, especially for the multiplier. + *

              + * + * @param initialNonZeroOddNumber + * a non-zero, odd number used as the initial value. This will be the returned + * value if no fields are found to include in the hash code + * @param multiplierNonZeroOddNumber + * a non-zero, odd number used as the multiplier + * @param object + * the Object to create a hashCode for + * @param testTransients + * whether to include transient fields + * @return int hash code + * @throws IllegalArgumentException + * if the Object is null + * @throws IllegalArgumentException + * if the number is zero or even + */ + public static int reflectionHashCode(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final Object object, + final boolean testTransients) { + return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, testTransients, null); + } + + /** + *

              + * Uses reflection to build a valid hash code from the fields of {@code object}. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * If the TestTransients parameter is set to true, transient members will be tested, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

              + * + *

              + * Static fields will not be included. Superclass fields will be included up to and including the specified + * superclass. A null superclass is treated as java.lang.Object. + *

              + * + *

              + * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class, + * however this is not vital. Prime numbers are preferred, especially for the multiplier. + *

              + * + * @param + * the type of the object involved + * @param initialNonZeroOddNumber + * a non-zero, odd number used as the initial value. This will be the returned + * value if no fields are found to include in the hash code + * @param multiplierNonZeroOddNumber + * a non-zero, odd number used as the multiplier + * @param object + * the Object to create a hashCode for + * @param testTransients + * whether to include transient fields + * @param reflectUpToClass + * the superclass to reflect up to (inclusive), may be null + * @param excludeFields + * array of field names to exclude from use in calculation of hash code + * @return int hash code + * @throws IllegalArgumentException + * if the Object is null + * @throws IllegalArgumentException + * if the number is zero or even + * @since 2.0 + */ + public static int reflectionHashCode(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final T object, + final boolean testTransients, final Class reflectUpToClass, final String... excludeFields) { + + if (object == null) { + throw new IllegalArgumentException("The object to build a hash code for must not be null"); + } + final HashCodeBuilder builder = new HashCodeBuilder(initialNonZeroOddNumber, multiplierNonZeroOddNumber); + Class clazz = object.getClass(); + reflectionAppend(object, clazz, builder, testTransients, excludeFields); + while (clazz.getSuperclass() != null && clazz != reflectUpToClass) { + clazz = clazz.getSuperclass(); + reflectionAppend(object, clazz, builder, testTransients, excludeFields); + } + return builder.toHashCode(); + } + + /** + *

              + * Uses reflection to build a valid hash code from the fields of {@code object}. + *

              + * + *

              + * This constructor uses two hard coded choices for the constants needed to build a hash code. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * If the TestTransients parameter is set to true, transient members will be tested, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

              + * + *

              + * Static fields will not be tested. Superclass fields will be included. If no fields are found to include + * in the hash code, the result of this method will be constant. + *

              + * + * @param object + * the Object to create a hashCode for + * @param testTransients + * whether to include transient fields + * @return int hash code + * @throws IllegalArgumentException + * if the object is null + */ + public static int reflectionHashCode(final Object object, final boolean testTransients) { + return reflectionHashCode(DEFAULT_INITIAL_VALUE, DEFAULT_MULTIPLIER_VALUE, object, + testTransients, null); + } + + /** + *

              + * Uses reflection to build a valid hash code from the fields of {@code object}. + *

              + * + *

              + * This constructor uses two hard coded choices for the constants needed to build a hash code. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * Transient members will be not be used, as they are likely derived fields, and not part of the value of the + * Object. + *

              + * + *

              + * Static fields will not be tested. Superclass fields will be included. If no fields are found to include + * in the hash code, the result of this method will be constant. + *

              + * + * @param object + * the Object to create a hashCode for + * @param excludeFields + * Collection of String field names to exclude from use in calculation of hash code + * @return int hash code + * @throws IllegalArgumentException + * if the object is null + */ + public static int reflectionHashCode(final Object object, final Collection excludeFields) { + return reflectionHashCode(object, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); + } + + // ------------------------------------------------------------------------- + + /** + *

              + * Uses reflection to build a valid hash code from the fields of {@code object}. + *

              + * + *

              + * This constructor uses two hard coded choices for the constants needed to build a hash code. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * Transient members will be not be used, as they are likely derived fields, and not part of the value of the + * Object. + *

              + * + *

              + * Static fields will not be tested. Superclass fields will be included. If no fields are found to include + * in the hash code, the result of this method will be constant. + *

              + * + * @param object + * the Object to create a hashCode for + * @param excludeFields + * array of field names to exclude from use in calculation of hash code + * @return int hash code + * @throws IllegalArgumentException + * if the object is null + */ + public static int reflectionHashCode(final Object object, final String... excludeFields) { + return reflectionHashCode(DEFAULT_INITIAL_VALUE, DEFAULT_MULTIPLIER_VALUE, object, false, + null, excludeFields); + } + + /** + *

              + * Registers the given object. Used by the reflection methods to avoid infinite loops. + *

              + * + * @param value + * The object to register. + */ + static void register(final Object value) { + synchronized (HashCodeBuilder.class) { + if (getRegistry() == null) { + REGISTRY.set(new HashSet()); + } + } + getRegistry().add(new IDKey(value)); + } + + /** + *

              + * Unregisters the given object. + *

              + * + *

              + * Used by the reflection methods to avoid infinite loops. + * + * @param value + * The object to unregister. + * @since 2.3 + */ + static void unregister(final Object value) { + Set registry = getRegistry(); + if (registry != null) { + registry.remove(new IDKey(value)); + synchronized (HashCodeBuilder.class) { + //read again + registry = getRegistry(); + if (registry != null && registry.isEmpty()) { + REGISTRY.remove(); + } + } + } + } + + /** + * Constant to use in building the hashCode. + */ + private final int iConstant; + + /** + * Running total of the hashCode. + */ + private int iTotal = 0; + + /** + *

              + * Uses two hard coded choices for the constants needed to build a hashCode. + *

              + */ + public HashCodeBuilder() { + iConstant = 37; + iTotal = 17; + } + + /** + *

              + * Two randomly chosen, odd numbers must be passed in. Ideally these should be different for each class, + * however this is not vital. + *

              + * + *

              + * Prime numbers are preferred, especially for the multiplier. + *

              + * + * @param initialOddNumber + * an odd number used as the initial value + * @param multiplierOddNumber + * an odd number used as the multiplier + * @throws IllegalArgumentException + * if the number is even + */ + public HashCodeBuilder(final int initialOddNumber, final int multiplierOddNumber) { + Validate.isTrue(initialOddNumber % 2 != 0, "HashCodeBuilder requires an odd initial value"); + Validate.isTrue(multiplierOddNumber % 2 != 0, "HashCodeBuilder requires an odd multiplier"); + iConstant = multiplierOddNumber; + iTotal = initialOddNumber; + } + + /** + *

              + * Append a hashCode for a boolean. + *

              + *

              + * This adds 1 when true, and 0 when false to the hashCode. + *

              + *

              + * This is in contrast to the standard java.lang.Boolean.hashCode handling, which computes + * a hashCode value of 1231 for java.lang.Boolean instances + * that represent true or 1237 for java.lang.Boolean instances + * that represent false. + *

              + *

              + * This is in accordance with the Effective Java design. + *

              + * + * @param value + * the boolean to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final boolean value) { + iTotal = iTotal * iConstant + (value ? 0 : 1); + return this; + } + + /** + *

              + * Append a hashCode for a boolean array. + *

              + * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final boolean[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (final boolean element : array) { + append(element); + } + } + return this; + } + + // ------------------------------------------------------------------------- + + /** + *

              + * Append a hashCode for a byte. + *

              + * + * @param value + * the byte to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final byte value) { + iTotal = iTotal * iConstant + value; + return this; + } + + // ------------------------------------------------------------------------- + + /** + *

              + * Append a hashCode for a byte array. + *

              + * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final byte[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (final byte element : array) { + append(element); + } + } + return this; + } + + /** + *

              + * Append a hashCode for a char. + *

              + * + * @param value + * the char to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final char value) { + iTotal = iTotal * iConstant + value; + return this; + } + + /** + *

              + * Append a hashCode for a char array. + *

              + * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final char[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (final char element : array) { + append(element); + } + } + return this; + } + + /** + *

              + * Append a hashCode for a double. + *

              + * + * @param value + * the double to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final double value) { + return append(Double.doubleToLongBits(value)); + } + + /** + *

              + * Append a hashCode for a double array. + *

              + * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final double[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (final double element : array) { + append(element); + } + } + return this; + } + + /** + *

              + * Append a hashCode for a float. + *

              + * + * @param value + * the float to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final float value) { + iTotal = iTotal * iConstant + Float.floatToIntBits(value); + return this; + } + + /** + *

              + * Append a hashCode for a float array. + *

              + * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final float[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (final float element : array) { + append(element); + } + } + return this; + } + + /** + *

              + * Append a hashCode for an int. + *

              + * + * @param value + * the int to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final int value) { + iTotal = iTotal * iConstant + value; + return this; + } + + /** + *

              + * Append a hashCode for an int array. + *

              + * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final int[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (final int element : array) { + append(element); + } + } + return this; + } + + /** + *

              + * Append a hashCode for a long. + *

              + * + * @param value + * the long to add to the hashCode + * @return this + */ + // NOTE: This method uses >> and not >>> as Effective Java and + // Long.hashCode do. Ideally we should switch to >>> at + // some stage. There are backwards compat issues, so + // that will have to wait for the time being. cf LANG-342. + public HashCodeBuilder append(final long value) { + iTotal = iTotal * iConstant + ((int) (value ^ (value >> 32))); + return this; + } + + /** + *

              + * Append a hashCode for a long array. + *

              + * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final long[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (final long element : array) { + append(element); + } + } + return this; + } + + /** + *

              + * Append a hashCode for an Object. + *

              + * + * @param object + * the Object to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final Object object) { + if (object == null) { + iTotal = iTotal * iConstant; + + } else { + if(object.getClass().isArray()) { + // 'Switch' on type of array, to dispatch to the correct handler + // This handles multi dimensional arrays + if (object instanceof long[]) { + append((long[]) object); + } else if (object instanceof int[]) { + append((int[]) object); + } else if (object instanceof short[]) { + append((short[]) object); + } else if (object instanceof char[]) { + append((char[]) object); + } else if (object instanceof byte[]) { + append((byte[]) object); + } else if (object instanceof double[]) { + append((double[]) object); + } else if (object instanceof float[]) { + append((float[]) object); + } else if (object instanceof boolean[]) { + append((boolean[]) object); + } else { + // Not an array of primitives + append((Object[]) object); + } + } else { + iTotal = iTotal * iConstant + object.hashCode(); + } + } + return this; + } + + /** + *

              + * Append a hashCode for an Object array. + *

              + * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final Object[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (final Object element : array) { + append(element); + } + } + return this; + } + + /** + *

              + * Append a hashCode for a short. + *

              + * + * @param value + * the short to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final short value) { + iTotal = iTotal * iConstant + value; + return this; + } + + /** + *

              + * Append a hashCode for a short array. + *

              + * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(final short[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (final short element : array) { + append(element); + } + } + return this; + } + + /** + *

              + * Adds the result of super.hashCode() to this builder. + *

              + * + * @param superHashCode + * the result of calling super.hashCode() + * @return this HashCodeBuilder, used to chain calls. + * @since 2.0 + */ + public HashCodeBuilder appendSuper(final int superHashCode) { + iTotal = iTotal * iConstant + superHashCode; + return this; + } + + /** + *

              + * Return the computed hashCode. + *

              + * + * @return hashCode based on the fields appended + */ + public int toHashCode() { + return iTotal; + } + + /** + * Returns the computed hashCode. + * + * @return hashCode based on the fields appended + * + * @since 3.0 + */ + @Override + public Integer build() { + return Integer.valueOf(toHashCode()); + } + + /** + *

              + * The computed hashCode from toHashCode() is returned due to the likelihood + * of bugs in mis-calling toHashCode() and the unlikeliness of it mattering what the hashCode for + * HashCodeBuilder itself is.

              + * + * @return hashCode based on the fields appended + * @since 2.5 + */ + @Override + public int hashCode() { + return toHashCode(); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/IDKey.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/IDKey.java new file mode 100644 index 000000000..7e2d29a25 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/IDKey.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.fr.third.org.apache.commons.lang3.builder; + +// adapted from org.apache.axis.utils.IDKey + +/** + * Wrap an identity key (System.identityHashCode()) + * so that an object can only be equal() to itself. + * + * This is necessary to disambiguate the occasional duplicate + * identityHashCodes that can occur. + * + */ +final class IDKey { + private final Object value; + private final int id; + + /** + * Constructor for IDKey + * @param _value The value + */ + public IDKey(final Object _value) { + // This is the Object hashcode + id = System.identityHashCode(_value); + // There have been some cases (LANG-459) that return the + // same identity hash code for different objects. So + // the value is also added to disambiguate these cases. + value = _value; + } + + /** + * returns hashcode - i.e. the system identity hashcode. + * @return the hashcode + */ + @Override + public int hashCode() { + return id; + } + + /** + * checks if instances are equal + * @param other The other object to compare to + * @return if the instances are for the same object + */ + @Override + public boolean equals(final Object other) { + if (!(other instanceof IDKey)) { + return false; + } + final IDKey idKey = (IDKey) other; + if (id != idKey.id) { + return false; + } + // Note that identity equals is used. + return value == idKey.value; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java new file mode 100644 index 000000000..a5d4a54eb --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.lang3.builder; + +import com.fr.third.org.apache.commons.lang3.ClassUtils; +import com.fr.third.org.apache.commons.lang3.SystemUtils; + +/** + *

              Works with {@link ToStringBuilder} to create a "deep" toString. + * But instead a single line like the {@link RecursiveToStringStyle} this creates a multiline String + * similar to the {@link ToStringStyle#MULTI_LINE_STYLE}.

              + * + *

              To use this class write code as follows:

              + * + *
              + * public class Job {
              + *   String title;
              + *   ...
              + * }
              + * 
              + * public class Person {
              + *   String name;
              + *   int age;
              + *   boolean smoker;
              + *   Job job;
              + * 
              + *   ...
              + * 
              + *   public String toString() {
              + *     return new ReflectionToStringBuilder(this, new MultilineRecursiveToStringStyle()).toString();
              + *   }
              + * }
              + * 
              + * + *

              + * This will produce a toString of the format:
              + * Person@7f54[
              + *   name=Stephen,
              + *   age=29,
              + *   smoker=false,
              + *   job=Job@43cd2[
              + *     title=Manager
              + *   ]
              + * ] + *
              + *

              + * + * @since 3.4 + * @version $Id: MultilineRecursiveToStringStyle.java 1654142 2015-01-23 08:43:21Z britter $ + */ +class MultilineRecursiveToStringStyle extends RecursiveToStringStyle { + + /** + * Required for serialization support. + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** Indenting of inner lines. */ + private int indent = 2; + + /** Current indenting. */ + private int spaces = 2; + + /** + * Constructor. + */ + public MultilineRecursiveToStringStyle() { + super(); + resetIndent(); + } + + /** + * Resets the fields responsible for the line breaks and indenting. + * Must be invoked after changing the {@link #spaces} value. + */ + private void resetIndent() { + setArrayStart("{" + SystemUtils.LINE_SEPARATOR + spacer(spaces)); + setArraySeparator("," + SystemUtils.LINE_SEPARATOR + spacer(spaces)); + setArrayEnd(SystemUtils.LINE_SEPARATOR + spacer(spaces - indent) + "}"); + + setContentStart("[" + SystemUtils.LINE_SEPARATOR + spacer(spaces)); + setFieldSeparator("," + SystemUtils.LINE_SEPARATOR + spacer(spaces)); + setContentEnd(SystemUtils.LINE_SEPARATOR + spacer(spaces - indent) + "]"); + } + + /** + * Creates a StringBuilder responsible for the indenting. + * + * @param spaces how far to indent + * @return a StringBuilder with {spaces} leading space characters. + */ + private StringBuilder spacer(int spaces) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < spaces; i++) { + sb.append(" "); + } + return sb; + } + + @Override + public void appendDetail(StringBuffer buffer, String fieldName, Object value) { + if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && !String.class.equals(value.getClass()) + && accept(value.getClass())) { + spaces += indent; + resetIndent(); + buffer.append(ReflectionToStringBuilder.toString(value, this)); + spaces -= indent; + resetIndent(); + } else { + super.appendDetail(buffer, fieldName, value); + } + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final long[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final int[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final short[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final char[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final double[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final float[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + +} \ No newline at end of file diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/RecursiveToStringStyle.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/RecursiveToStringStyle.java new file mode 100644 index 000000000..202f5e757 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/RecursiveToStringStyle.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +import java.util.Collection; + +import com.fr.third.org.apache.commons.lang3.ClassUtils; + +/** + *

              Works with {@link ToStringBuilder} to create a "deep" toString.

              + * + *

              To use this class write code as follows:

              + * + *
              + * public class Job {
              + *   String title;
              + *   ...
              + * }
              + * 
              + * public class Person {
              + *   String name;
              + *   int age;
              + *   boolean smoker;
              + *   Job job;
              + *
              + *   ...
              + *
              + *   public String toString() {
              + *     return new ReflectionToStringBuilder(this, new RecursiveToStringStyle()).toString();
              + *   }
              + * }
              + * 
              + * + *

              This will produce a toString of the format: + * Person@7f54[name=Stephen,age=29,smoker=false,job=Job@43cd2[title=Manager]]

              + * + * @since 3.2 + * @version $Id: RecursiveToStringStyle.java 1606051 2014-06-27 12:22:17Z ggregory $ + */ +public class RecursiveToStringStyle extends ToStringStyle { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** + *

              Constructor.

              + */ + public RecursiveToStringStyle() { + super(); + } + + @Override + public void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { + if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && + !String.class.equals(value.getClass()) && + accept(value.getClass())) { + buffer.append(ReflectionToStringBuilder.toString(value, this)); + } else { + super.appendDetail(buffer, fieldName, value); + } + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection coll) { + appendClassName(buffer, coll); + appendIdentityHashCode(buffer, coll); + appendDetail(buffer, fieldName, coll.toArray()); + } + + /** + * Returns whether or not to recursively format the given Class. + * By default, this method always returns {@code true}, but may be overwritten by + * sub-classes to filter specific classes. + * + * @param clazz + * The class to test. + * @return Whether or not to recursively format the given Class. + */ + protected boolean accept(final Class clazz) { + return true; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java new file mode 100644 index 000000000..9e147102d --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java @@ -0,0 +1,696 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.lang3.builder; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; +import com.fr.third.org.apache.commons.lang3.ClassUtils; + +/** + *

              + * Assists in implementing {@link Object#toString()} methods using reflection. + *

              + *

              + * This class uses reflection to determine the fields to append. Because these fields are usually private, the class + * uses {@link java.lang.reflect.AccessibleObject#setAccessible(java.lang.reflect.AccessibleObject[], boolean)} to + * change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions are + * set up correctly. + *

              + *

              + * Using reflection to access (private) fields circumvents any synchronization protection guarding access to these + * fields. If a toString method cannot safely read a field, you should exclude it from the toString method, or use + * synchronization consistent with the class' lock management around the invocation of the method. Take special care to + * exclude non-thread-safe collection classes, because these classes may throw ConcurrentModificationException if + * modified while the toString method is executing. + *

              + *

              + * A typical invocation for this method would look like: + *

              + *
              + * public String toString() {
              + *     return ReflectionToStringBuilder.toString(this);
              + * }
              + * 
              + *

              + * You can also use the builder to debug 3rd party objects: + *

              + *
              + * System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));
              + * 
              + *

              + * A subclass can control field output by overriding the methods: + *

              + *
                + *
              • {@link #accept(java.lang.reflect.Field)}
              • + *
              • {@link #getValue(java.lang.reflect.Field)}
              • + *
              + *

              + * For example, this method does not include the password field in the returned String: + *

              + *
              + * public String toString() {
              + *     return (new ReflectionToStringBuilder(this) {
              + *         protected boolean accept(Field f) {
              + *             return super.accept(f) && !f.getName().equals("password");
              + *         }
              + *     }).toString();
              + * }
              + * 
              + *

              + * The exact format of the toString is determined by the {@link ToStringStyle} passed into the constructor. + *

              + * + *

              + * Note: the default {@link ToStringStyle} will only do a "shallow" formatting, i.e. composed objects are not + * further traversed. To get "deep" formatting, use an instance of {@link RecursiveToStringStyle}. + *

              + * + * @since 2.0 + * @version $Id: ReflectionToStringBuilder.java 1583482 2014-03-31 22:54:57Z niallp $ + */ +public class ReflectionToStringBuilder extends ToStringBuilder { + + /** + *

              + * Builds a toString value using the default ToStringStyle through reflection. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * Transient members will be not be included, as they are likely derived. Static fields will not be included. + * Superclass fields will be appended. + *

              + * + * @param object + * the Object to be output + * @return the String result + * @throws IllegalArgumentException + * if the Object is null + */ + public static String toString(final Object object) { + return toString(object, null, false, false, null); + } + + /** + *

              + * Builds a toString value through reflection. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * Transient members will be not be included, as they are likely derived. Static fields will not be included. + * Superclass fields will be appended. + *

              + * + *

              + * If the style is null, the default ToStringStyle is used. + *

              + * + * @param object + * the Object to be output + * @param style + * the style of the toString to create, may be null + * @return the String result + * @throws IllegalArgumentException + * if the Object or ToStringStyle is null + */ + public static String toString(final Object object, final ToStringStyle style) { + return toString(object, style, false, false, null); + } + + /** + *

              + * Builds a toString value through reflection. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * If the outputTransients is true, transient members will be output, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

              + * + *

              + * Static fields will not be included. Superclass fields will be appended. + *

              + * + *

              + * If the style is null, the default ToStringStyle is used. + *

              + * + * @param object + * the Object to be output + * @param style + * the style of the toString to create, may be null + * @param outputTransients + * whether to include transient fields + * @return the String result + * @throws IllegalArgumentException + * if the Object is null + */ + public static String toString(final Object object, final ToStringStyle style, final boolean outputTransients) { + return toString(object, style, outputTransients, false, null); + } + + /** + *

              + * Builds a toString value through reflection. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * If the outputTransients is true, transient fields will be output, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

              + * + *

              + * If the outputStatics is true, static fields will be output, otherwise they are + * ignored. + *

              + * + *

              + * Static fields will not be included. Superclass fields will be appended. + *

              + * + *

              + * If the style is null, the default ToStringStyle is used. + *

              + * + * @param object + * the Object to be output + * @param style + * the style of the toString to create, may be null + * @param outputTransients + * whether to include transient fields + * @param outputStatics + * whether to include transient fields + * @return the String result + * @throws IllegalArgumentException + * if the Object is null + * @since 2.1 + */ + public static String toString(final Object object, final ToStringStyle style, final boolean outputTransients, final boolean outputStatics) { + return toString(object, style, outputTransients, outputStatics, null); + } + + /** + *

              + * Builds a toString value through reflection. + *

              + * + *

              + * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

              + * + *

              + * If the outputTransients is true, transient fields will be output, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

              + * + *

              + * If the outputStatics is true, static fields will be output, otherwise they are + * ignored. + *

              + * + *

              + * Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as + * java.lang.Object. + *

              + * + *

              + * If the style is null, the default ToStringStyle is used. + *

              + * + * @param + * the type of the object + * @param object + * the Object to be output + * @param style + * the style of the toString to create, may be null + * @param outputTransients + * whether to include transient fields + * @param outputStatics + * whether to include static fields + * @param reflectUpToClass + * the superclass to reflect up to (inclusive), may be null + * @return the String result + * @throws IllegalArgumentException + * if the Object is null + * @since 2.1 + */ + public static String toString( + final T object, final ToStringStyle style, final boolean outputTransients, + final boolean outputStatics, final Class reflectUpToClass) { + return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics) + .toString(); + } + + /** + * Builds a String for a toString method excluding the given field names. + * + * @param object + * The object to "toString". + * @param excludeFieldNames + * The field names to exclude. Null excludes nothing. + * @return The toString value. + */ + public static String toStringExclude(final Object object, final Collection excludeFieldNames) { + return toStringExclude(object, toNoNullStringArray(excludeFieldNames)); + } + + /** + * Converts the given Collection into an array of Strings. The returned array does not contain null + * entries. Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} if an array element + * is null. + * + * @param collection + * The collection to convert + * @return A new array of Strings. + */ + static String[] toNoNullStringArray(final Collection collection) { + if (collection == null) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + return toNoNullStringArray(collection.toArray()); + } + + /** + * Returns a new array of Strings without null elements. Internal method used to normalize exclude lists + * (arrays and collections). Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} + * if an array element is null. + * + * @param array + * The array to check + * @return The given array or a new array without null. + */ + static String[] toNoNullStringArray(final Object[] array) { + final List list = new ArrayList(array.length); + for (final Object e : array) { + if (e != null) { + list.add(e.toString()); + } + } + return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY); + } + + + /** + * Builds a String for a toString method excluding the given field names. + * + * @param object + * The object to "toString". + * @param excludeFieldNames + * The field names to exclude + * @return The toString value. + */ + public static String toStringExclude(final Object object, final String... excludeFieldNames) { + return new ReflectionToStringBuilder(object).setExcludeFieldNames(excludeFieldNames).toString(); + } + + /** + * Whether or not to append static fields. + */ + private boolean appendStatics = false; + + /** + * Whether or not to append transient fields. + */ + private boolean appendTransients = false; + + /** + * Which field names to exclude from output. Intended for fields like "password". + * + * @since 3.0 this is protected instead of private + */ + protected String[] excludeFieldNames; + + /** + * The last super class to stop appending fields for. + */ + private Class upToClass = null; + + /** + *

              + * Constructor. + *

              + * + *

              + * This constructor outputs using the default style set with setDefaultStyle. + *

              + * + * @param object + * the Object to build a toString for, must not be null + * @throws IllegalArgumentException + * if the Object passed in is null + */ + public ReflectionToStringBuilder(final Object object) { + super(object); + } + + /** + *

              + * Constructor. + *

              + * + *

              + * If the style is null, the default style is used. + *

              + * + * @param object + * the Object to build a toString for, must not be null + * @param style + * the style of the toString to create, may be null + * @throws IllegalArgumentException + * if the Object passed in is null + */ + public ReflectionToStringBuilder(final Object object, final ToStringStyle style) { + super(object, style); + } + + /** + *

              + * Constructor. + *

              + * + *

              + * If the style is null, the default style is used. + *

              + * + *

              + * If the buffer is null, a new one is created. + *

              + * + * @param object + * the Object to build a toString for + * @param style + * the style of the toString to create, may be null + * @param buffer + * the StringBuffer to populate, may be null + * @throws IllegalArgumentException + * if the Object passed in is null + */ + public ReflectionToStringBuilder(final Object object, final ToStringStyle style, final StringBuffer buffer) { + super(object, style, buffer); + } + + /** + * Constructor. + * + * @param + * the type of the object + * @param object + * the Object to build a toString for + * @param style + * the style of the toString to create, may be null + * @param buffer + * the StringBuffer to populate, may be null + * @param reflectUpToClass + * the superclass to reflect up to (inclusive), may be null + * @param outputTransients + * whether to include transient fields + * @param outputStatics + * whether to include static fields + * @since 2.1 + */ + public ReflectionToStringBuilder( + final T object, final ToStringStyle style, final StringBuffer buffer, + final Class reflectUpToClass, final boolean outputTransients, final boolean outputStatics) { + super(object, style, buffer); + this.setUpToClass(reflectUpToClass); + this.setAppendTransients(outputTransients); + this.setAppendStatics(outputStatics); + } + + /** + * Returns whether or not to append the given Field. + *
                + *
              • Transient fields are appended only if {@link #isAppendTransients()} returns true. + *
              • Static fields are appended only if {@link #isAppendStatics()} returns true. + *
              • Inner class fields are not appended.
              • + *
              + * + * @param field + * The Field to test. + * @return Whether or not to append the given Field. + */ + protected boolean accept(final Field field) { + if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) { + // Reject field from inner class. + return false; + } + if (Modifier.isTransient(field.getModifiers()) && !this.isAppendTransients()) { + // Reject transient fields. + return false; + } + if (Modifier.isStatic(field.getModifiers()) && !this.isAppendStatics()) { + // Reject static fields. + return false; + } + if (this.excludeFieldNames != null + && Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) { + // Reject fields from the getExcludeFieldNames list. + return false; + } + return true; + } + + /** + *

              + * Appends the fields and values defined by the given object of the given Class. + *

              + * + *

              + * If a cycle is detected as an object is "toString()'ed", such an object is rendered as if + * Object.toString() had been called and not implemented by the object. + *

              + * + * @param clazz + * The class of object parameter + */ + protected void appendFieldsIn(final Class clazz) { + if (clazz.isArray()) { + this.reflectionAppendArray(this.getObject()); + return; + } + final Field[] fields = clazz.getDeclaredFields(); + AccessibleObject.setAccessible(fields, true); + for (final Field field : fields) { + final String fieldName = field.getName(); + if (this.accept(field)) { + try { + // Warning: Field.get(Object) creates wrappers objects + // for primitive types. + final Object fieldValue = this.getValue(field); + this.append(fieldName, fieldValue); + } catch (final IllegalAccessException ex) { + //this can't happen. Would get a Security exception + // instead + //throw a runtime exception in case the impossible + // happens. + throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage()); + } + } + } + } + + /** + * @return Returns the excludeFieldNames. + */ + public String[] getExcludeFieldNames() { + return this.excludeFieldNames.clone(); + } + + /** + *

              + * Gets the last super class to stop appending fields for. + *

              + * + * @return The last super class to stop appending fields for. + */ + public Class getUpToClass() { + return this.upToClass; + } + + /** + *

              + * Calls java.lang.reflect.Field.get(Object). + *

              + * + * @param field + * The Field to query. + * @return The Object from the given Field. + * + * @throws IllegalArgumentException + * see {@link java.lang.reflect.Field#get(Object)} + * @throws IllegalAccessException + * see {@link java.lang.reflect.Field#get(Object)} + * + * @see java.lang.reflect.Field#get(Object) + */ + protected Object getValue(final Field field) throws IllegalArgumentException, IllegalAccessException { + return field.get(this.getObject()); + } + + /** + *

              + * Gets whether or not to append static fields. + *

              + * + * @return Whether or not to append static fields. + * @since 2.1 + */ + public boolean isAppendStatics() { + return this.appendStatics; + } + + /** + *

              + * Gets whether or not to append transient fields. + *

              + * + * @return Whether or not to append transient fields. + */ + public boolean isAppendTransients() { + return this.appendTransients; + } + + /** + *

              + * Append to the toString an Object array. + *

              + * + * @param array + * the array to add to the toString + * @return this + */ + public ReflectionToStringBuilder reflectionAppendArray(final Object array) { + this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array); + return this; + } + + /** + *

              + * Sets whether or not to append static fields. + *

              + * + * @param appendStatics + * Whether or not to append static fields. + * @since 2.1 + */ + public void setAppendStatics(final boolean appendStatics) { + this.appendStatics = appendStatics; + } + + /** + *

              + * Sets whether or not to append transient fields. + *

              + * + * @param appendTransients + * Whether or not to append transient fields. + */ + public void setAppendTransients(final boolean appendTransients) { + this.appendTransients = appendTransients; + } + + /** + * Sets the field names to exclude. + * + * @param excludeFieldNamesParam + * The excludeFieldNames to excluding from toString or null. + * @return this + */ + public ReflectionToStringBuilder setExcludeFieldNames(final String... excludeFieldNamesParam) { + if (excludeFieldNamesParam == null) { + this.excludeFieldNames = null; + } else { + //clone and remove nulls + this.excludeFieldNames = toNoNullStringArray(excludeFieldNamesParam); + Arrays.sort(this.excludeFieldNames); + } + return this; + } + + /** + *

              + * Sets the last super class to stop appending fields for. + *

              + * + * @param clazz + * The last super class to stop appending fields for. + */ + public void setUpToClass(final Class clazz) { + if (clazz != null) { + final Object object = getObject(); + if (object != null && clazz.isInstance(object) == false) { + throw new IllegalArgumentException("Specified class is not a superclass of the object"); + } + } + this.upToClass = clazz; + } + + /** + *

              + * Gets the String built by this builder. + *

              + * + * @return the built string + */ + @Override + public String toString() { + if (this.getObject() == null) { + return this.getStyle().getNullText(); + } + Class clazz = this.getObject().getClass(); + this.appendFieldsIn(clazz); + while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) { + clazz = clazz.getSuperclass(); + this.appendFieldsIn(clazz); + } + return super.toString(); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/StandardToStringStyle.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/StandardToStringStyle.java new file mode 100644 index 000000000..ad3a9df48 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/StandardToStringStyle.java @@ -0,0 +1,560 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +/** + *

              Works with {@link ToStringBuilder} to create a toString.

              + * + *

              This class is intended to be used as a singleton. + * There is no need to instantiate a new style each time. + * Simply instantiate the class once, customize the values as required, and + * store the result in a public static final variable for the rest of the + * program to access.

              + * + * @since 1.0 + * @version $Id: StandardToStringStyle.java 1583482 2014-03-31 22:54:57Z niallp $ + */ +public class StandardToStringStyle extends ToStringStyle { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** + *

              Constructor.

              + */ + public StandardToStringStyle() { + super(); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to use the class name.

              + * + * @return the current useClassName flag + */ + @Override + public boolean isUseClassName() { // NOPMD as this is implementing the abstract class + return super.isUseClassName(); + } + + /** + *

              Sets whether to use the class name.

              + * + * @param useClassName the new useClassName flag + */ + @Override + public void setUseClassName(final boolean useClassName) { // NOPMD as this is implementing the abstract class + super.setUseClassName(useClassName); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to output short or long class names.

              + * + * @return the current useShortClassName flag + * @since 2.0 + */ + @Override + public boolean isUseShortClassName() { // NOPMD as this is implementing the abstract class + return super.isUseShortClassName(); + } + + /** + *

              Sets whether to output short or long class names.

              + * + * @param useShortClassName the new useShortClassName flag + * @since 2.0 + */ + @Override + public void setUseShortClassName(final boolean useShortClassName) { // NOPMD as this is implementing the abstract class + super.setUseShortClassName(useShortClassName); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to use the identity hash code.

              + * @return the current useIdentityHashCode flag + */ + @Override + public boolean isUseIdentityHashCode() { // NOPMD as this is implementing the abstract class + return super.isUseIdentityHashCode(); + } + + /** + *

              Sets whether to use the identity hash code.

              + * + * @param useIdentityHashCode the new useIdentityHashCode flag + */ + @Override + public void setUseIdentityHashCode(final boolean useIdentityHashCode) { // NOPMD as this is implementing the abstract class + super.setUseIdentityHashCode(useIdentityHashCode); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to use the field names passed in.

              + * + * @return the current useFieldNames flag + */ + @Override + public boolean isUseFieldNames() { // NOPMD as this is implementing the abstract class + return super.isUseFieldNames(); + } + + /** + *

              Sets whether to use the field names passed in.

              + * + * @param useFieldNames the new useFieldNames flag + */ + @Override + public void setUseFieldNames(final boolean useFieldNames) { // NOPMD as this is implementing the abstract class + super.setUseFieldNames(useFieldNames); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to use full detail when the caller doesn't + * specify.

              + * + * @return the current defaultFullDetail flag + */ + @Override + public boolean isDefaultFullDetail() { // NOPMD as this is implementing the abstract class + return super.isDefaultFullDetail(); + } + + /** + *

              Sets whether to use full detail when the caller doesn't + * specify.

              + * + * @param defaultFullDetail the new defaultFullDetail flag + */ + @Override + public void setDefaultFullDetail(final boolean defaultFullDetail) { // NOPMD as this is implementing the abstract class + super.setDefaultFullDetail(defaultFullDetail); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to output array content detail.

              + * + * @return the current array content detail setting + */ + @Override + public boolean isArrayContentDetail() { // NOPMD as this is implementing the abstract class + return super.isArrayContentDetail(); + } + + /** + *

              Sets whether to output array content detail.

              + * + * @param arrayContentDetail the new arrayContentDetail flag + */ + @Override + public void setArrayContentDetail(final boolean arrayContentDetail) { // NOPMD as this is implementing the abstract class + super.setArrayContentDetail(arrayContentDetail); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the array start text.

              + * + * @return the current array start text + */ + @Override + public String getArrayStart() { // NOPMD as this is implementing the abstract class + return super.getArrayStart(); + } + + /** + *

              Sets the array start text.

              + * + *

              null is accepted, but will be converted + * to an empty String.

              + * + * @param arrayStart the new array start text + */ + @Override + public void setArrayStart(final String arrayStart) { // NOPMD as this is implementing the abstract class + super.setArrayStart(arrayStart); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the array end text.

              + * + * @return the current array end text + */ + @Override + public String getArrayEnd() { // NOPMD as this is implementing the abstract class + return super.getArrayEnd(); + } + + /** + *

              Sets the array end text.

              + * + *

              null is accepted, but will be converted + * to an empty String.

              + * + * @param arrayEnd the new array end text + */ + @Override + public void setArrayEnd(final String arrayEnd) { // NOPMD as this is implementing the abstract class + super.setArrayEnd(arrayEnd); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the array separator text.

              + * + * @return the current array separator text + */ + @Override + public String getArraySeparator() { // NOPMD as this is implementing the abstract class + return super.getArraySeparator(); + } + + /** + *

              Sets the array separator text.

              + * + *

              null is accepted, but will be converted + * to an empty String.

              + * + * @param arraySeparator the new array separator text + */ + @Override + public void setArraySeparator(final String arraySeparator) { // NOPMD as this is implementing the abstract class + super.setArraySeparator(arraySeparator); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the content start text.

              + * + * @return the current content start text + */ + @Override + public String getContentStart() { // NOPMD as this is implementing the abstract class + return super.getContentStart(); + } + + /** + *

              Sets the content start text.

              + * + *

              null is accepted, but will be converted + * to an empty String.

              + * + * @param contentStart the new content start text + */ + @Override + public void setContentStart(final String contentStart) { // NOPMD as this is implementing the abstract class + super.setContentStart(contentStart); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the content end text.

              + * + * @return the current content end text + */ + @Override + public String getContentEnd() { // NOPMD as this is implementing the abstract class + return super.getContentEnd(); + } + + /** + *

              Sets the content end text.

              + * + *

              null is accepted, but will be converted + * to an empty String.

              + * + * @param contentEnd the new content end text + */ + @Override + public void setContentEnd(final String contentEnd) { // NOPMD as this is implementing the abstract class + super.setContentEnd(contentEnd); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the field name value separator text.

              + * + * @return the current field name value separator text + */ + @Override + public String getFieldNameValueSeparator() { // NOPMD as this is implementing the abstract class + return super.getFieldNameValueSeparator(); + } + + /** + *

              Sets the field name value separator text.

              + * + *

              null is accepted, but will be converted + * to an empty String.

              + * + * @param fieldNameValueSeparator the new field name value separator text + */ + @Override + public void setFieldNameValueSeparator(final String fieldNameValueSeparator) { // NOPMD as this is implementing the abstract class + super.setFieldNameValueSeparator(fieldNameValueSeparator); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the field separator text.

              + * + * @return the current field separator text + */ + @Override + public String getFieldSeparator() { // NOPMD as this is implementing the abstract class + return super.getFieldSeparator(); + } + + /** + *

              Sets the field separator text.

              + * + *

              null is accepted, but will be converted + * to an empty String.

              + * + * @param fieldSeparator the new field separator text + */ + @Override + public void setFieldSeparator(final String fieldSeparator) { // NOPMD as this is implementing the abstract class + super.setFieldSeparator(fieldSeparator); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether the field separator should be added at the start + * of each buffer.

              + * + * @return the fieldSeparatorAtStart flag + * @since 2.0 + */ + @Override + public boolean isFieldSeparatorAtStart() { // NOPMD as this is implementing the abstract class + return super.isFieldSeparatorAtStart(); + } + + /** + *

              Sets whether the field separator should be added at the start + * of each buffer.

              + * + * @param fieldSeparatorAtStart the fieldSeparatorAtStart flag + * @since 2.0 + */ + @Override + public void setFieldSeparatorAtStart(final boolean fieldSeparatorAtStart) { // NOPMD as this is implementing the abstract class + super.setFieldSeparatorAtStart(fieldSeparatorAtStart); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether the field separator should be added at the end + * of each buffer.

              + * + * @return fieldSeparatorAtEnd flag + * @since 2.0 + */ + @Override + public boolean isFieldSeparatorAtEnd() { // NOPMD as this is implementing the abstract class + return super.isFieldSeparatorAtEnd(); + } + + /** + *

              Sets whether the field separator should be added at the end + * of each buffer.

              + * + * @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag + * @since 2.0 + */ + @Override + public void setFieldSeparatorAtEnd(final boolean fieldSeparatorAtEnd) { // NOPMD as this is implementing the abstract class + super.setFieldSeparatorAtEnd(fieldSeparatorAtEnd); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the text to output when null found.

              + * + * @return the current text to output when null found + */ + @Override + public String getNullText() { // NOPMD as this is implementing the abstract class + return super.getNullText(); + } + + /** + *

              Sets the text to output when null found.

              + * + *

              null is accepted, but will be converted + * to an empty String.

              + * + * @param nullText the new text to output when null found + */ + @Override + public void setNullText(final String nullText) { // NOPMD as this is implementing the abstract class + super.setNullText(nullText); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the text to output when a Collection, + * Map or Array size is output.

              + * + *

              This is output before the size value.

              + * + * @return the current start of size text + */ + @Override + public String getSizeStartText() { // NOPMD as this is implementing the abstract class + return super.getSizeStartText(); + } + + /** + *

              Sets the start text to output when a Collection, + * Map or Array size is output.

              + * + *

              This is output before the size value.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param sizeStartText the new start of size text + */ + @Override + public void setSizeStartText(final String sizeStartText) { // NOPMD as this is implementing the abstract class + super.setSizeStartText(sizeStartText); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the end text to output when a Collection, + * Map or Array size is output.

              + * + *

              This is output after the size value.

              + * + * @return the current end of size text + */ + @Override + public String getSizeEndText() { // NOPMD as this is implementing the abstract class + return super.getSizeEndText(); + } + + /** + *

              Sets the end text to output when a Collection, + * Map or Array size is output.

              + * + *

              This is output after the size value.

              + * + *

              null is accepted, but will be converted + * to an empty String.

              + * + * @param sizeEndText the new end of size text + */ + @Override + public void setSizeEndText(final String sizeEndText) { // NOPMD as this is implementing the abstract class + super.setSizeEndText(sizeEndText); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the start text to output when an Object is + * output in summary mode.

              + * + *

              This is output before the size value.

              + * + * @return the current start of summary text + */ + @Override + public String getSummaryObjectStartText() { // NOPMD as this is implementing the abstract class + return super.getSummaryObjectStartText(); + } + + /** + *

              Sets the start text to output when an Object is + * output in summary mode.

              + * + *

              This is output before the size value.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param summaryObjectStartText the new start of summary text + */ + @Override + public void setSummaryObjectStartText(final String summaryObjectStartText) { // NOPMD as this is implementing the abstract class + super.setSummaryObjectStartText(summaryObjectStartText); + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the end text to output when an Object is + * output in summary mode.

              + * + *

              This is output after the size value.

              + * + * @return the current end of summary text + */ + @Override + public String getSummaryObjectEndText() { // NOPMD as this is implementing the abstract class + return super.getSummaryObjectEndText(); + } + + /** + *

              Sets the end text to output when an Object is + * output in summary mode.

              + * + *

              This is output after the size value.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param summaryObjectEndText the new end of summary text + */ + @Override + public void setSummaryObjectEndText(final String summaryObjectEndText) { // NOPMD as this is implementing the abstract class + super.setSummaryObjectEndText(summaryObjectEndText); + } + + //--------------------------------------------------------------------- + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ToStringBuilder.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ToStringBuilder.java new file mode 100644 index 000000000..518627550 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ToStringBuilder.java @@ -0,0 +1,1080 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +import com.fr.third.org.apache.commons.lang3.ObjectUtils; + +/** + *

              Assists in implementing {@link Object#toString()} methods.

              + * + *

              This class enables a good and consistent toString() to be built for any + * class or object. This class aims to simplify the process by:

              + *
                + *
              • allowing field names
              • + *
              • handling all types consistently
              • + *
              • handling nulls consistently
              • + *
              • outputting arrays and multi-dimensional arrays
              • + *
              • enabling the detail level to be controlled for Objects and Collections
              • + *
              • handling class hierarchies
              • + *
              + * + *

              To use this class write code as follows:

              + * + *
              + * public class Person {
              + *   String name;
              + *   int age;
              + *   boolean smoker;
              + *
              + *   ...
              + *
              + *   public String toString() {
              + *     return new ToStringBuilder(this).
              + *       append("name", name).
              + *       append("age", age).
              + *       append("smoker", smoker).
              + *       toString();
              + *   }
              + * }
              + * 
              + * + *

              This will produce a toString of the format: + * Person@7f54[name=Stephen,age=29,smoker=false]

              + * + *

              To add the superclass toString, use {@link #appendSuper}. + * To append the toString from an object that is delegated + * to (or any other object), use {@link #appendToString}.

              + * + *

              Alternatively, there is a method that uses reflection to determine + * the fields to test. Because these fields are usually private, the method, + * reflectionToString, uses AccessibleObject.setAccessible to + * change the visibility of the fields. This will fail under a security manager, + * unless the appropriate permissions are set up correctly. It is also + * slower than testing explicitly.

              + * + *

              A typical invocation for this method would look like:

              + * + *
              + * public String toString() {
              + *   return ToStringBuilder.reflectionToString(this);
              + * }
              + * 
              + * + *

              You can also use the builder to debug 3rd party objects:

              + * + *
              + * System.out.println("An object: " + ToStringBuilder.reflectionToString(anObject));
              + * 
              + * + *

              The exact format of the toString is determined by + * the {@link ToStringStyle} passed into the constructor.

              + * + * @since 1.0 + * @version $Id: ToStringBuilder.java 1533551 2013-10-18 16:49:15Z sebb $ + */ +public class ToStringBuilder implements Builder { + + /** + * The default style of output to use, not null. + */ + private static volatile ToStringStyle defaultStyle = ToStringStyle.DEFAULT_STYLE; + + //---------------------------------------------------------------------------- + + /** + *

              Gets the default ToStringStyle to use.

              + * + *

              This method gets a singleton default value, typically for the whole JVM. + * Changing this default should generally only be done during application startup. + * It is recommended to pass a ToStringStyle to the constructor instead + * of using this global default.

              + * + *

              This method can be used from multiple threads. + * Internally, a volatile variable is used to provide the guarantee + * that the latest value set using {@link #setDefaultStyle} is the value returned. + * It is strongly recommended that the default style is only changed during application startup.

              + * + *

              One reason for changing the default could be to have a verbose style during + * development and a compact style in production.

              + * + * @return the default ToStringStyle, never null + */ + public static ToStringStyle getDefaultStyle() { + return defaultStyle; + } + + /** + *

              Sets the default ToStringStyle to use.

              + * + *

              This method sets a singleton default value, typically for the whole JVM. + * Changing this default should generally only be done during application startup. + * It is recommended to pass a ToStringStyle to the constructor instead + * of changing this global default.

              + * + *

              This method is not intended for use from multiple threads. + * Internally, a volatile variable is used to provide the guarantee + * that the latest value set is the value returned from {@link #getDefaultStyle}.

              + * + * @param style the default ToStringStyle + * @throws IllegalArgumentException if the style is null + */ + public static void setDefaultStyle(final ToStringStyle style) { + if (style == null) { + throw new IllegalArgumentException("The style must not be null"); + } + defaultStyle = style; + } + + //---------------------------------------------------------------------------- + /** + *

              Uses ReflectionToStringBuilder to generate a + * toString for the specified object.

              + * + * @param object the Object to be output + * @return the String result + * @see ReflectionToStringBuilder#toString(Object) + */ + public static String reflectionToString(final Object object) { + return ReflectionToStringBuilder.toString(object); + } + + /** + *

              Uses ReflectionToStringBuilder to generate a + * toString for the specified object.

              + * + * @param object the Object to be output + * @param style the style of the toString to create, may be null + * @return the String result + * @see ReflectionToStringBuilder#toString(Object,ToStringStyle) + */ + public static String reflectionToString(final Object object, final ToStringStyle style) { + return ReflectionToStringBuilder.toString(object, style); + } + + /** + *

              Uses ReflectionToStringBuilder to generate a + * toString for the specified object.

              + * + * @param object the Object to be output + * @param style the style of the toString to create, may be null + * @param outputTransients whether to include transient fields + * @return the String result + * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean) + */ + public static String reflectionToString(final Object object, final ToStringStyle style, final boolean outputTransients) { + return ReflectionToStringBuilder.toString(object, style, outputTransients, false, null); + } + + /** + *

              Uses ReflectionToStringBuilder to generate a + * toString for the specified object.

              + * + * @param the type of the object + * @param object the Object to be output + * @param style the style of the toString to create, may be null + * @param outputTransients whether to include transient fields + * @param reflectUpToClass the superclass to reflect up to (inclusive), may be null + * @return the String result + * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean,boolean,Class) + * @since 2.0 + */ + public static String reflectionToString( + final T object, + final ToStringStyle style, + final boolean outputTransients, + final Class reflectUpToClass) { + return ReflectionToStringBuilder.toString(object, style, outputTransients, false, reflectUpToClass); + } + + //---------------------------------------------------------------------------- + + /** + * Current toString buffer, not null. + */ + private final StringBuffer buffer; + /** + * The object being output, may be null. + */ + private final Object object; + /** + * The style of output to use, not null. + */ + private final ToStringStyle style; + + /** + *

              Constructs a builder for the specified object using the default output style.

              + * + *

              This default style is obtained from {@link #getDefaultStyle()}.

              + * + * @param object the Object to build a toString for, not recommended to be null + */ + public ToStringBuilder(final Object object) { + this(object, null, null); + } + + /** + *

              Constructs a builder for the specified object using the a defined output style.

              + * + *

              If the style is null, the default style is used.

              + * + * @param object the Object to build a toString for, not recommended to be null + * @param style the style of the toString to create, null uses the default style + */ + public ToStringBuilder(final Object object, final ToStringStyle style) { + this(object, style, null); + } + + /** + *

              Constructs a builder for the specified object.

              + * + *

              If the style is null, the default style is used.

              + * + *

              If the buffer is null, a new one is created.

              + * + * @param object the Object to build a toString for, not recommended to be null + * @param style the style of the toString to create, null uses the default style + * @param buffer the StringBuffer to populate, may be null + */ + public ToStringBuilder(final Object object, ToStringStyle style, StringBuffer buffer) { + if (style == null) { + style = getDefaultStyle(); + } + if (buffer == null) { + buffer = new StringBuffer(512); + } + this.buffer = buffer; + this.style = style; + this.object = object; + + style.appendStart(buffer, object); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a boolean + * value.

              + * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final boolean value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a boolean + * array.

              + * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final boolean[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a byte + * value.

              + * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final byte value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a byte + * array.

              + * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final byte[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a char + * value.

              + * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final char value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a char + * array.

              + * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final char[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a double + * value.

              + * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final double value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a double + * array.

              + * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final double[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a float + * value.

              + * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final float value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a float + * array.

              + * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final float[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString an int + * value.

              + * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final int value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString an int + * array.

              + * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final int[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a long + * value.

              + * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final long value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a long + * array.

              + * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final long[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString an Object + * value.

              + * + * @param obj the value to add to the toString + * @return this + */ + public ToStringBuilder append(final Object obj) { + style.append(buffer, null, obj, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString an Object + * array.

              + * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final Object[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a short + * value.

              + * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final short value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a short + * array.

              + * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final short[] array) { + style.append(buffer, null, array, null); + return this; + } + + /** + *

              Append to the toString a boolean + * value.

              + * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final boolean value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

              Append to the toString a boolean + * array.

              + * + * @param fieldName the field name + * @param array the array to add to the hashCode + * @return this + */ + public ToStringBuilder append(final String fieldName, final boolean[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

              Append to the toString a boolean + * array.

              + * + *

              A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(final String fieldName, final boolean[] array, final boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

              Append to the toString an byte + * value.

              + * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final byte value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

              Append to the toString a byte array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final byte[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

              Append to the toString a byte + * array.

              + * + *

              A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array. + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(final String fieldName, final byte[] array, final boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

              Append to the toString a char + * value.

              + * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final char value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

              Append to the toString a char + * array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final char[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

              Append to the toString a char + * array.

              + * + *

              A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(final String fieldName, final char[] array, final boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

              Append to the toString a double + * value.

              + * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final double value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

              Append to the toString a double + * array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final double[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

              Append to the toString a double + * array.

              + * + *

              A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(final String fieldName, final double[] array, final boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

              Append to the toString an float + * value.

              + * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final float value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

              Append to the toString a float + * array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final float[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

              Append to the toString a float + * array.

              + * + *

              A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(final String fieldName, final float[] array, final boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

              Append to the toString an int + * value.

              + * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final int value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

              Append to the toString an int + * array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final int[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

              Append to the toString an int + * array.

              + * + *

              A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(final String fieldName, final int[] array, final boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

              Append to the toString a long + * value.

              + * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final long value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

              Append to the toString a long + * array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final long[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

              Append to the toString a long + * array.

              + * + *

              A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(final String fieldName, final long[] array, final boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

              Append to the toString an Object + * value.

              + * + * @param fieldName the field name + * @param obj the value to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final Object obj) { + style.append(buffer, fieldName, obj, null); + return this; + } + + /** + *

              Append to the toString an Object + * value.

              + * + * @param fieldName the field name + * @param obj the value to add to the toString + * @param fullDetail true for detail, + * false for summary info + * @return this + */ + public ToStringBuilder append(final String fieldName, final Object obj, final boolean fullDetail) { + style.append(buffer, fieldName, obj, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

              Append to the toString an Object + * array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final Object[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

              Append to the toString an Object + * array.

              + * + *

              A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(final String fieldName, final Object[] array, final boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

              Append to the toString an short + * value.

              + * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final short value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

              Append to the toString a short + * array.

              + * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(final String fieldName, final short[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

              Append to the toString a short + * array.

              + * + *

              A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array. + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(final String fieldName, final short[] array, final boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

              Appends with the same format as the default Object toString() + * method. Appends the class name followed by + * {@link System#identityHashCode(java.lang.Object)}.

              + * + * @param srcObject the Object whose class name and id to output + * @return this + * @since 2.0 + */ + public ToStringBuilder appendAsObjectToString(final Object srcObject) { + ObjectUtils.identityToString(this.getStringBuffer(), srcObject); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

              Append the toString from the superclass.

              + * + *

              This method assumes that the superclass uses the same ToStringStyle + * as this one.

              + * + *

              If superToString is null, no change is made.

              + * + * @param superToString the result of super.toString() + * @return this + * @since 2.0 + */ + public ToStringBuilder appendSuper(final String superToString) { + if (superToString != null) { + style.appendSuper(buffer, superToString); + } + return this; + } + + /** + *

              Append the toString from another object.

              + * + *

              This method is useful where a class delegates most of the implementation of + * its properties to another class. You can then call toString() on + * the other class and pass the result into this method.

              + * + *
              +     *   private AnotherObject delegate;
              +     *   private String fieldInThisClass;
              +     *
              +     *   public String toString() {
              +     *     return new ToStringBuilder(this).
              +     *       appendToString(delegate.toString()).
              +     *       append(fieldInThisClass).
              +     *       toString();
              +     *   }
              + * + *

              This method assumes that the other object uses the same ToStringStyle + * as this one.

              + * + *

              If the toString is null, no change is made.

              + * + * @param toString the result of toString() on another object + * @return this + * @since 2.0 + */ + public ToStringBuilder appendToString(final String toString) { + if (toString != null) { + style.appendToString(buffer, toString); + } + return this; + } + + /** + *

              Returns the Object being output.

              + * + * @return The object being output. + * @since 2.0 + */ + public Object getObject() { + return object; + } + + /** + *

              Gets the StringBuffer being populated.

              + * + * @return the StringBuffer being populated + */ + public StringBuffer getStringBuffer() { + return buffer; + } + + //---------------------------------------------------------------------------- + + /** + *

              Gets the ToStringStyle being used.

              + * + * @return the ToStringStyle being used + * @since 2.0 + */ + public ToStringStyle getStyle() { + return style; + } + + /** + *

              Returns the built toString.

              + * + *

              This method appends the end of data indicator, and can only be called once. + * Use {@link #getStringBuffer} to get the current string state.

              + * + *

              If the object is null, return the style's nullText

              + * + * @return the String toString + */ + @Override + public String toString() { + if (this.getObject() == null) { + this.getStringBuffer().append(this.getStyle().getNullText()); + } else { + style.appendEnd(this.getStringBuffer(), this.getObject()); + } + return this.getStringBuffer().toString(); + } + + /** + * Returns the String that was build as an object representation. The + * default implementation utilizes the {@link #toString()} implementation. + * + * @return the String toString + * + * @see #toString() + * + * @since 3.0 + */ + @Override + public String build() { + return toString(); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ToStringStyle.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ToStringStyle.java new file mode 100644 index 000000000..7138ea5da --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/ToStringStyle.java @@ -0,0 +1,2614 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.builder; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Map; +import java.util.WeakHashMap; + +import com.fr.third.org.apache.commons.lang3.ClassUtils; +import com.fr.third.org.apache.commons.lang3.ObjectUtils; +import com.fr.third.org.apache.commons.lang3.SystemUtils; + +/** + *

              Controls String formatting for {@link ToStringBuilder}. + * The main public interface is always via ToStringBuilder.

              + * + *

              These classes are intended to be used as Singletons. + * There is no need to instantiate a new style each time. A program + * will generally use one of the predefined constants on this class. + * Alternatively, the {@link StandardToStringStyle} class can be used + * to set the individual settings. Thus most styles can be achieved + * without subclassing.

              + * + *

              If required, a subclass can override as many or as few of the + * methods as it requires. Each object type (from boolean + * to long to Object to int[]) has + * its own methods to output it. Most have two versions, detail and summary. + * + *

              For example, the detail version of the array based methods will + * output the whole array, whereas the summary method will just output + * the array length.

              + * + *

              If you want to format the output of certain objects, such as dates, you + * must create a subclass and override a method. + *

              + *
              + * public class MyStyle extends ToStringStyle {
              + *   protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
              + *     if (value instanceof Date) {
              + *       value = new SimpleDateFormat("yyyy-MM-dd").format(value);
              + *     }
              + *     buffer.append(value);
              + *   }
              + * }
              + * 
              + * + * @since 1.0 + * @version $Id: ToStringStyle.java 1654143 2015-01-23 08:45:21Z britter $ + */ +public abstract class ToStringStyle implements Serializable { + + /** + * Serialization version ID. + */ + private static final long serialVersionUID = -2587890625525655916L; + + /** + * The default toString style. Using the Person + * example from {@link ToStringBuilder}, the output would look like this: + * + *
              +     * Person@182f0db[name=John Doe,age=33,smoker=false]
              +     * 
              + */ + public static final ToStringStyle DEFAULT_STYLE = new DefaultToStringStyle(); + + /** + * The multi line toString style. Using the Person + * example from {@link ToStringBuilder}, the output would look like this: + * + *
              +     * Person@182f0db[
              +     *   name=John Doe
              +     *   age=33
              +     *   smoker=false
              +     * ]
              +     * 
              + */ + public static final ToStringStyle MULTI_LINE_STYLE = new MultiLineToStringStyle(); + + /** + * The no field names toString style. Using the + * Person example from {@link ToStringBuilder}, the output + * would look like this: + * + *
              +     * Person@182f0db[John Doe,33,false]
              +     * 
              + */ + public static final ToStringStyle NO_FIELD_NAMES_STYLE = new NoFieldNameToStringStyle(); + + /** + * The short prefix toString style. Using the Person example + * from {@link ToStringBuilder}, the output would look like this: + * + *
              +     * Person[name=John Doe,age=33,smoker=false]
              +     * 
              + * + * @since 2.1 + */ + public static final ToStringStyle SHORT_PREFIX_STYLE = new ShortPrefixToStringStyle(); + + /** + * The simple toString style. Using the Person + * example from {@link ToStringBuilder}, the output would look like this: + * + *
              +     * John Doe,33,false
              +     * 
              + */ + public static final ToStringStyle SIMPLE_STYLE = new SimpleToStringStyle(); + + /** + * The no class name toString style. Using the Person + * example from {@link ToStringBuilder}, the output would look like this: + * + *
              +     * [name=John Doe,age=33,smoker=false]
              +     * 
              + * + * @since 3.4 + */ + public static final ToStringStyle NO_CLASS_NAME_STYLE = new NoClassNameToStringStyle(); + + /** + * The JSON toString style. Using the Person example from + * {@link ToStringBuilder}, the output would look like this: + * + *
              +     * {"name": "John Doe", "age": 33, "smoker": true}
              +     * 
              + * + * Note: Since field names are mandatory in JSON, this + * ToStringStyle will throw an {@link UnsupportedOperationException} if no + * field name is passed in while appending. Furthermore This ToStringStyle + * will only generate valid JSON if referenced objects also produce JSON + * when calling {@code toString()} on them. + * + * @since 3.4 + * @see json.org + */ + public static final ToStringStyle JSON_STYLE = new JsonToStringStyle(); + + /** + *

              + * A registry of objects used by reflectionToString methods + * to detect cyclical object references and avoid infinite loops. + *

              + */ + private static final ThreadLocal> REGISTRY = + new ThreadLocal>(); + /* + * Note that objects of this class are generally shared between threads, so + * an instance variable would not be suitable here. + * + * In normal use the registry should always be left empty, because the caller + * should call toString() which will clean up. + * + * See LANG-792 + */ + + /** + *

              + * Returns the registry of objects being traversed by the reflectionToString + * methods in the current thread. + *

              + * + * @return Set the registry of objects being traversed + */ + static Map getRegistry() { + return REGISTRY.get(); + } + + /** + *

              + * Returns true if the registry contains the given object. + * Used by the reflection methods to avoid infinite loops. + *

              + * + * @param value + * The object to lookup in the registry. + * @return boolean true if the registry contains the given + * object. + */ + static boolean isRegistered(final Object value) { + final Map m = getRegistry(); + return m != null && m.containsKey(value); + } + + /** + *

              + * Registers the given object. Used by the reflection methods to avoid + * infinite loops. + *

              + * + * @param value + * The object to register. + */ + static void register(final Object value) { + if (value != null) { + final Map m = getRegistry(); + if (m == null) { + REGISTRY.set(new WeakHashMap()); + } + getRegistry().put(value, null); + } + } + + /** + *

              + * Unregisters the given object. + *

              + * + *

              + * Used by the reflection methods to avoid infinite loops. + *

              + * + * @param value + * The object to unregister. + */ + static void unregister(final Object value) { + if (value != null) { + final Map m = getRegistry(); + if (m != null) { + m.remove(value); + if (m.isEmpty()) { + REGISTRY.remove(); + } + } + } + } + + /** + * Whether to use the field names, the default is true. + */ + private boolean useFieldNames = true; + + /** + * Whether to use the class name, the default is true. + */ + private boolean useClassName = true; + + /** + * Whether to use short class names, the default is false. + */ + private boolean useShortClassName = false; + + /** + * Whether to use the identity hash code, the default is true. + */ + private boolean useIdentityHashCode = true; + + /** + * The content start '['. + */ + private String contentStart = "["; + + /** + * The content end ']'. + */ + private String contentEnd = "]"; + + /** + * The field name value separator '='. + */ + private String fieldNameValueSeparator = "="; + + /** + * Whether the field separator should be added before any other fields. + */ + private boolean fieldSeparatorAtStart = false; + + /** + * Whether the field separator should be added after any other fields. + */ + private boolean fieldSeparatorAtEnd = false; + + /** + * The field separator ','. + */ + private String fieldSeparator = ","; + + /** + * The array start '{'. + */ + private String arrayStart = "{"; + + /** + * The array separator ','. + */ + private String arraySeparator = ","; + + /** + * The detail for array content. + */ + private boolean arrayContentDetail = true; + + /** + * The array end '}'. + */ + private String arrayEnd = "}"; + + /** + * The value to use when fullDetail is null, + * the default value is true. + */ + private boolean defaultFullDetail = true; + + /** + * The null text '<null>'. + */ + private String nullText = ""; + + /** + * The summary size text start '<size'. + */ + private String sizeStartText = "'>'. + */ + private String sizeEndText = ">"; + + /** + * The summary object text start '<'. + */ + private String summaryObjectStartText = "<"; + + /** + * The summary object text start '>'. + */ + private String summaryObjectEndText = ">"; + + //---------------------------------------------------------------------------- + + /** + *

              Constructor.

              + */ + protected ToStringStyle() { + super(); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString the superclass toString.

              + *

              NOTE: It assumes that the toString has been created from the same ToStringStyle.

              + * + *

              A null superToString is ignored.

              + * + * @param buffer the StringBuffer to populate + * @param superToString the super.toString() + * @since 2.0 + */ + public void appendSuper(final StringBuffer buffer, final String superToString) { + appendToString(buffer, superToString); + } + + /** + *

              Append to the toString another toString.

              + *

              NOTE: It assumes that the toString has been created from the same ToStringStyle.

              + * + *

              A null toString is ignored.

              + * + * @param buffer the StringBuffer to populate + * @param toString the additional toString + * @since 2.0 + */ + public void appendToString(final StringBuffer buffer, final String toString) { + if (toString != null) { + final int pos1 = toString.indexOf(contentStart) + contentStart.length(); + final int pos2 = toString.lastIndexOf(contentEnd); + if (pos1 != pos2 && pos1 >= 0 && pos2 >= 0) { + final String data = toString.substring(pos1, pos2); + if (fieldSeparatorAtStart) { + removeLastFieldSeparator(buffer); + } + buffer.append(data); + appendFieldSeparator(buffer); + } + } + } + + /** + *

              Append to the toString the start of data indicator.

              + * + * @param buffer the StringBuffer to populate + * @param object the Object to build a toString for + */ + public void appendStart(final StringBuffer buffer, final Object object) { + if (object != null) { + appendClassName(buffer, object); + appendIdentityHashCode(buffer, object); + appendContentStart(buffer); + if (fieldSeparatorAtStart) { + appendFieldSeparator(buffer); + } + } + } + + /** + *

              Append to the toString the end of data indicator.

              + * + * @param buffer the StringBuffer to populate + * @param object the Object to build a + * toString for. + */ + public void appendEnd(final StringBuffer buffer, final Object object) { + if (this.fieldSeparatorAtEnd == false) { + removeLastFieldSeparator(buffer); + } + appendContentEnd(buffer); + unregister(object); + } + + /** + *

              Remove the last field separator from the buffer.

              + * + * @param buffer the StringBuffer to populate + * @since 2.0 + */ + protected void removeLastFieldSeparator(final StringBuffer buffer) { + final int len = buffer.length(); + final int sepLen = fieldSeparator.length(); + if (len > 0 && sepLen > 0 && len >= sepLen) { + boolean match = true; + for (int i = 0; i < sepLen; i++) { + if (buffer.charAt(len - 1 - i) != fieldSeparator.charAt(sepLen - 1 - i)) { + match = false; + break; + } + } + if (match) { + buffer.setLength(len - sepLen); + } + } + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString an Object + * value, printing the full toString of the + * Object passed in.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + * @param fullDetail true for detail, false + * for summary info, null for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final Object value, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (value == null) { + appendNullText(buffer, fieldName); + + } else { + appendInternal(buffer, fieldName, value, isFullDetail(fullDetail)); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString an Object, + * correctly interpreting its type.

              + * + *

              This method performs the main lookup by Class type to correctly + * route arrays, Collections, Maps and + * Objects to the appropriate method.

              + * + *

              Either detail or summary views can be specified.

              + * + *

              If a cycle is detected, an object will be appended with the + * Object.toString() format.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString, + * not null + * @param detail output detail or not + */ + protected void appendInternal(final StringBuffer buffer, final String fieldName, final Object value, final boolean detail) { + if (isRegistered(value) + && !(value instanceof Number || value instanceof Boolean || value instanceof Character)) { + appendCyclicObject(buffer, fieldName, value); + return; + } + + register(value); + + try { + if (value instanceof Collection) { + if (detail) { + appendDetail(buffer, fieldName, (Collection) value); + } else { + appendSummarySize(buffer, fieldName, ((Collection) value).size()); + } + + } else if (value instanceof Map) { + if (detail) { + appendDetail(buffer, fieldName, (Map) value); + } else { + appendSummarySize(buffer, fieldName, ((Map) value).size()); + } + + } else if (value instanceof long[]) { + if (detail) { + appendDetail(buffer, fieldName, (long[]) value); + } else { + appendSummary(buffer, fieldName, (long[]) value); + } + + } else if (value instanceof int[]) { + if (detail) { + appendDetail(buffer, fieldName, (int[]) value); + } else { + appendSummary(buffer, fieldName, (int[]) value); + } + + } else if (value instanceof short[]) { + if (detail) { + appendDetail(buffer, fieldName, (short[]) value); + } else { + appendSummary(buffer, fieldName, (short[]) value); + } + + } else if (value instanceof byte[]) { + if (detail) { + appendDetail(buffer, fieldName, (byte[]) value); + } else { + appendSummary(buffer, fieldName, (byte[]) value); + } + + } else if (value instanceof char[]) { + if (detail) { + appendDetail(buffer, fieldName, (char[]) value); + } else { + appendSummary(buffer, fieldName, (char[]) value); + } + + } else if (value instanceof double[]) { + if (detail) { + appendDetail(buffer, fieldName, (double[]) value); + } else { + appendSummary(buffer, fieldName, (double[]) value); + } + + } else if (value instanceof float[]) { + if (detail) { + appendDetail(buffer, fieldName, (float[]) value); + } else { + appendSummary(buffer, fieldName, (float[]) value); + } + + } else if (value instanceof boolean[]) { + if (detail) { + appendDetail(buffer, fieldName, (boolean[]) value); + } else { + appendSummary(buffer, fieldName, (boolean[]) value); + } + + } else if (value.getClass().isArray()) { + if (detail) { + appendDetail(buffer, fieldName, (Object[]) value); + } else { + appendSummary(buffer, fieldName, (Object[]) value); + } + + } else { + if (detail) { + appendDetail(buffer, fieldName, value); + } else { + appendSummary(buffer, fieldName, value); + } + } + } finally { + unregister(value); + } + } + + /** + *

              Append to the toString an Object + * value that has been detected to participate in a cycle. This + * implementation will print the standard string value of the value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString, + * not null + * + * @since 2.2 + */ + protected void appendCyclicObject(final StringBuffer buffer, final String fieldName, final Object value) { + ObjectUtils.identityToString(buffer, value); + } + + /** + *

              Append to the toString an Object + * value, printing the full detail of the Object.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { + buffer.append(value); + } + + /** + *

              Append to the toString a Collection.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param coll the Collection to add to the + * toString, not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection coll) { + buffer.append(coll); + } + + /** + *

              Append to the toString a Map.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param map the Map to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Map map) { + buffer.append(map); + } + + /** + *

              Append to the toString an Object + * value, printing a summary of the Object.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString, + * not null + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final Object value) { + buffer.append(summaryObjectStartText); + buffer.append(getShortClassName(value.getClass())); + buffer.append(summaryObjectEndText); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a long + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(final StringBuffer buffer, final String fieldName, final long value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString a long + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final long value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString an int + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(final StringBuffer buffer, final String fieldName, final int value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString an int + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final int value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a short + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(final StringBuffer buffer, final String fieldName, final short value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString a short + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final short value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a byte + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(final StringBuffer buffer, final String fieldName, final byte value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString a byte + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a char + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(final StringBuffer buffer, final String fieldName, final char value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString a char + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final char value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a double + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(final StringBuffer buffer, final String fieldName, final double value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString a double + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final double value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a float + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(final StringBuffer buffer, final String fieldName, final float value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString a float + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final float value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a boolean + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(final StringBuffer buffer, final String fieldName, final boolean value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString a boolean + * value.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean value) { + buffer.append(value); + } + + /** + *

              Append to the toString an Object + * array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final Object[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString the detail of an + * Object array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + final Object item = array[i]; + if (i > 0) { + buffer.append(arraySeparator); + } + if (item == null) { + appendNullText(buffer, fieldName); + + } else { + appendInternal(buffer, fieldName, item, arrayContentDetail); + } + } + buffer.append(arrayEnd); + } + + /** + *

              Append to the toString the detail of an array type.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + * @since 2.0 + */ + protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) { + buffer.append(arrayStart); + final int length = Array.getLength(array); + for (int i = 0; i < length; i++) { + final Object item = Array.get(array, i); + if (i > 0) { + buffer.append(arraySeparator); + } + if (item == null) { + appendNullText(buffer, fieldName); + + } else { + appendInternal(buffer, fieldName, item, arrayContentDetail); + } + } + buffer.append(arrayEnd); + } + + /** + *

              Append to the toString a summary of an + * Object array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final Object[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a long + * array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final long[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString the detail of a + * long array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final long[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + *

              Append to the toString a summary of a + * long array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final long[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString an int + * array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final int[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString the detail of an + * int array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final int[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + *

              Append to the toString a summary of an + * int array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final int[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a short + * array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final short[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString the detail of a + * short array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final short[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + *

              Append to the toString a summary of a + * short array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final short[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a byte + * array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final byte[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString the detail of a + * byte array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + *

              Append to the toString a summary of a + * byte array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final byte[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a char + * array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final char[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString the detail of a + * char array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final char[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + *

              Append to the toString a summary of a + * char array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final char[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a double + * array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final double[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString the detail of a + * double array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final double[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + *

              Append to the toString a summary of a + * double array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final double[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a float + * array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final float[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString the detail of a + * float array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final float[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + *

              Append to the toString a summary of a + * float array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final float[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString a boolean + * array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final boolean[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + *

              Append to the toString the detail of a + * boolean array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + *

              Append to the toString a summary of a + * boolean array.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final boolean[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

              Append to the toString the class name.

              + * + * @param buffer the StringBuffer to populate + * @param object the Object whose name to output + */ + protected void appendClassName(final StringBuffer buffer, final Object object) { + if (useClassName && object != null) { + register(object); + if (useShortClassName) { + buffer.append(getShortClassName(object.getClass())); + } else { + buffer.append(object.getClass().getName()); + } + } + } + + /** + *

              Append the {@link System#identityHashCode(java.lang.Object)}.

              + * + * @param buffer the StringBuffer to populate + * @param object the Object whose id to output + */ + protected void appendIdentityHashCode(final StringBuffer buffer, final Object object) { + if (this.isUseIdentityHashCode() && object!=null) { + register(object); + buffer.append('@'); + buffer.append(Integer.toHexString(System.identityHashCode(object))); + } + } + + /** + *

              Append to the toString the content start.

              + * + * @param buffer the StringBuffer to populate + */ + protected void appendContentStart(final StringBuffer buffer) { + buffer.append(contentStart); + } + + /** + *

              Append to the toString the content end.

              + * + * @param buffer the StringBuffer to populate + */ + protected void appendContentEnd(final StringBuffer buffer) { + buffer.append(contentEnd); + } + + /** + *

              Append to the toString an indicator for null.

              + * + *

              The default indicator is '<null>'.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + */ + protected void appendNullText(final StringBuffer buffer, final String fieldName) { + buffer.append(nullText); + } + + /** + *

              Append to the toString the field separator.

              + * + * @param buffer the StringBuffer to populate + */ + protected void appendFieldSeparator(final StringBuffer buffer) { + buffer.append(fieldSeparator); + } + + /** + *

              Append to the toString the field start.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + */ + protected void appendFieldStart(final StringBuffer buffer, final String fieldName) { + if (useFieldNames && fieldName != null) { + buffer.append(fieldName); + buffer.append(fieldNameValueSeparator); + } + } + + /** + *

              Append to the toString the field end.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + */ + protected void appendFieldEnd(final StringBuffer buffer, final String fieldName) { + appendFieldSeparator(buffer); + } + + /** + *

              Append to the toString a size summary.

              + * + *

              The size summary is used to summarize the contents of + * Collections, Maps and arrays.

              + * + *

              The output consists of a prefix, the passed in size + * and a suffix.

              + * + *

              The default format is '<size=n>'.

              + * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param size the size to append + */ + protected void appendSummarySize(final StringBuffer buffer, final String fieldName, final int size) { + buffer.append(sizeStartText); + buffer.append(size); + buffer.append(sizeEndText); + } + + /** + *

              Is this field to be output in full detail.

              + * + *

              This method converts a detail request into a detail level. + * The calling code may request full detail (true), + * but a subclass might ignore that and always return + * false. The calling code may pass in + * null indicating that it doesn't care about + * the detail level. In this case the default detail level is + * used.

              + * + * @param fullDetailRequest the detail level requested + * @return whether full detail is to be shown + */ + protected boolean isFullDetail(final Boolean fullDetailRequest) { + if (fullDetailRequest == null) { + return defaultFullDetail; + } + return fullDetailRequest.booleanValue(); + } + + /** + *

              Gets the short class name for a class.

              + * + *

              The short class name is the classname excluding + * the package name.

              + * + * @param cls the Class to get the short name of + * @return the short name + */ + protected String getShortClassName(final Class cls) { + return ClassUtils.getShortClassName(cls); + } + + // Setters and getters for the customizable parts of the style + // These methods are not expected to be overridden, except to make public + // (They are not public so that immutable subclasses can be written) + //--------------------------------------------------------------------- + + /** + *

              Gets whether to use the class name.

              + * + * @return the current useClassName flag + */ + protected boolean isUseClassName() { + return useClassName; + } + + /** + *

              Sets whether to use the class name.

              + * + * @param useClassName the new useClassName flag + */ + protected void setUseClassName(final boolean useClassName) { + this.useClassName = useClassName; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to output short or long class names.

              + * + * @return the current useShortClassName flag + * @since 2.0 + */ + protected boolean isUseShortClassName() { + return useShortClassName; + } + + /** + *

              Sets whether to output short or long class names.

              + * + * @param useShortClassName the new useShortClassName flag + * @since 2.0 + */ + protected void setUseShortClassName(final boolean useShortClassName) { + this.useShortClassName = useShortClassName; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to use the identity hash code.

              + * + * @return the current useIdentityHashCode flag + */ + protected boolean isUseIdentityHashCode() { + return useIdentityHashCode; + } + + /** + *

              Sets whether to use the identity hash code.

              + * + * @param useIdentityHashCode the new useIdentityHashCode flag + */ + protected void setUseIdentityHashCode(final boolean useIdentityHashCode) { + this.useIdentityHashCode = useIdentityHashCode; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to use the field names passed in.

              + * + * @return the current useFieldNames flag + */ + protected boolean isUseFieldNames() { + return useFieldNames; + } + + /** + *

              Sets whether to use the field names passed in.

              + * + * @param useFieldNames the new useFieldNames flag + */ + protected void setUseFieldNames(final boolean useFieldNames) { + this.useFieldNames = useFieldNames; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to use full detail when the caller doesn't + * specify.

              + * + * @return the current defaultFullDetail flag + */ + protected boolean isDefaultFullDetail() { + return defaultFullDetail; + } + + /** + *

              Sets whether to use full detail when the caller doesn't + * specify.

              + * + * @param defaultFullDetail the new defaultFullDetail flag + */ + protected void setDefaultFullDetail(final boolean defaultFullDetail) { + this.defaultFullDetail = defaultFullDetail; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether to output array content detail.

              + * + * @return the current array content detail setting + */ + protected boolean isArrayContentDetail() { + return arrayContentDetail; + } + + /** + *

              Sets whether to output array content detail.

              + * + * @param arrayContentDetail the new arrayContentDetail flag + */ + protected void setArrayContentDetail(final boolean arrayContentDetail) { + this.arrayContentDetail = arrayContentDetail; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the array start text.

              + * + * @return the current array start text + */ + protected String getArrayStart() { + return arrayStart; + } + + /** + *

              Sets the array start text.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param arrayStart the new array start text + */ + protected void setArrayStart(String arrayStart) { + if (arrayStart == null) { + arrayStart = ""; + } + this.arrayStart = arrayStart; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the array end text.

              + * + * @return the current array end text + */ + protected String getArrayEnd() { + return arrayEnd; + } + + /** + *

              Sets the array end text.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param arrayEnd the new array end text + */ + protected void setArrayEnd(String arrayEnd) { + if (arrayEnd == null) { + arrayEnd = ""; + } + this.arrayEnd = arrayEnd; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the array separator text.

              + * + * @return the current array separator text + */ + protected String getArraySeparator() { + return arraySeparator; + } + + /** + *

              Sets the array separator text.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param arraySeparator the new array separator text + */ + protected void setArraySeparator(String arraySeparator) { + if (arraySeparator == null) { + arraySeparator = ""; + } + this.arraySeparator = arraySeparator; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the content start text.

              + * + * @return the current content start text + */ + protected String getContentStart() { + return contentStart; + } + + /** + *

              Sets the content start text.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param contentStart the new content start text + */ + protected void setContentStart(String contentStart) { + if (contentStart == null) { + contentStart = ""; + } + this.contentStart = contentStart; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the content end text.

              + * + * @return the current content end text + */ + protected String getContentEnd() { + return contentEnd; + } + + /** + *

              Sets the content end text.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param contentEnd the new content end text + */ + protected void setContentEnd(String contentEnd) { + if (contentEnd == null) { + contentEnd = ""; + } + this.contentEnd = contentEnd; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the field name value separator text.

              + * + * @return the current field name value separator text + */ + protected String getFieldNameValueSeparator() { + return fieldNameValueSeparator; + } + + /** + *

              Sets the field name value separator text.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param fieldNameValueSeparator the new field name value separator text + */ + protected void setFieldNameValueSeparator(String fieldNameValueSeparator) { + if (fieldNameValueSeparator == null) { + fieldNameValueSeparator = ""; + } + this.fieldNameValueSeparator = fieldNameValueSeparator; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the field separator text.

              + * + * @return the current field separator text + */ + protected String getFieldSeparator() { + return fieldSeparator; + } + + /** + *

              Sets the field separator text.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param fieldSeparator the new field separator text + */ + protected void setFieldSeparator(String fieldSeparator) { + if (fieldSeparator == null) { + fieldSeparator = ""; + } + this.fieldSeparator = fieldSeparator; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether the field separator should be added at the start + * of each buffer.

              + * + * @return the fieldSeparatorAtStart flag + * @since 2.0 + */ + protected boolean isFieldSeparatorAtStart() { + return fieldSeparatorAtStart; + } + + /** + *

              Sets whether the field separator should be added at the start + * of each buffer.

              + * + * @param fieldSeparatorAtStart the fieldSeparatorAtStart flag + * @since 2.0 + */ + protected void setFieldSeparatorAtStart(final boolean fieldSeparatorAtStart) { + this.fieldSeparatorAtStart = fieldSeparatorAtStart; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets whether the field separator should be added at the end + * of each buffer.

              + * + * @return fieldSeparatorAtEnd flag + * @since 2.0 + */ + protected boolean isFieldSeparatorAtEnd() { + return fieldSeparatorAtEnd; + } + + /** + *

              Sets whether the field separator should be added at the end + * of each buffer.

              + * + * @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag + * @since 2.0 + */ + protected void setFieldSeparatorAtEnd(final boolean fieldSeparatorAtEnd) { + this.fieldSeparatorAtEnd = fieldSeparatorAtEnd; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the text to output when null found.

              + * + * @return the current text to output when null found + */ + protected String getNullText() { + return nullText; + } + + /** + *

              Sets the text to output when null found.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param nullText the new text to output when null found + */ + protected void setNullText(String nullText) { + if (nullText == null) { + nullText = ""; + } + this.nullText = nullText; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the start text to output when a Collection, + * Map or array size is output.

              + * + *

              This is output before the size value.

              + * + * @return the current start of size text + */ + protected String getSizeStartText() { + return sizeStartText; + } + + /** + *

              Sets the start text to output when a Collection, + * Map or array size is output.

              + * + *

              This is output before the size value.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param sizeStartText the new start of size text + */ + protected void setSizeStartText(String sizeStartText) { + if (sizeStartText == null) { + sizeStartText = ""; + } + this.sizeStartText = sizeStartText; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the end text to output when a Collection, + * Map or array size is output.

              + * + *

              This is output after the size value.

              + * + * @return the current end of size text + */ + protected String getSizeEndText() { + return sizeEndText; + } + + /** + *

              Sets the end text to output when a Collection, + * Map or array size is output.

              + * + *

              This is output after the size value.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param sizeEndText the new end of size text + */ + protected void setSizeEndText(String sizeEndText) { + if (sizeEndText == null) { + sizeEndText = ""; + } + this.sizeEndText = sizeEndText; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the start text to output when an Object is + * output in summary mode.

              + * + *

              This is output before the size value.

              + * + * @return the current start of summary text + */ + protected String getSummaryObjectStartText() { + return summaryObjectStartText; + } + + /** + *

              Sets the start text to output when an Object is + * output in summary mode.

              + * + *

              This is output before the size value.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param summaryObjectStartText the new start of summary text + */ + protected void setSummaryObjectStartText(String summaryObjectStartText) { + if (summaryObjectStartText == null) { + summaryObjectStartText = ""; + } + this.summaryObjectStartText = summaryObjectStartText; + } + + //--------------------------------------------------------------------- + + /** + *

              Gets the end text to output when an Object is + * output in summary mode.

              + * + *

              This is output after the size value.

              + * + * @return the current end of summary text + */ + protected String getSummaryObjectEndText() { + return summaryObjectEndText; + } + + /** + *

              Sets the end text to output when an Object is + * output in summary mode.

              + * + *

              This is output after the size value.

              + * + *

              null is accepted, but will be converted to + * an empty String.

              + * + * @param summaryObjectEndText the new end of summary text + */ + protected void setSummaryObjectEndText(String summaryObjectEndText) { + if (summaryObjectEndText == null) { + summaryObjectEndText = ""; + } + this.summaryObjectEndText = summaryObjectEndText; + } + + //---------------------------------------------------------------------------- + + /** + *

              Default ToStringStyle.

              + * + *

              This is an inner class rather than using + * StandardToStringStyle to ensure its immutability.

              + */ + private static final class DefaultToStringStyle extends ToStringStyle { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** + *

              Constructor.

              + * + *

              Use the static constant rather than instantiating.

              + */ + DefaultToStringStyle() { + super(); + } + + /** + *

              Ensure Singleton after serialization.

              + * + * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.DEFAULT_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

              ToStringStyle that does not print out + * the field names.

              + * + *

              This is an inner class rather than using + * StandardToStringStyle to ensure its immutability. + */ + private static final class NoFieldNameToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + *

              Constructor.

              + * + *

              Use the static constant rather than instantiating.

              + */ + NoFieldNameToStringStyle() { + super(); + this.setUseFieldNames(false); + } + + /** + *

              Ensure Singleton after serialization.

              + * + * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.NO_FIELD_NAMES_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

              ToStringStyle that prints out the short + * class name and no identity hashcode.

              + * + *

              This is an inner class rather than using + * StandardToStringStyle to ensure its immutability.

              + */ + private static final class ShortPrefixToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + *

              Constructor.

              + * + *

              Use the static constant rather than instantiating.

              + */ + ShortPrefixToStringStyle() { + super(); + this.setUseShortClassName(true); + this.setUseIdentityHashCode(false); + } + + /** + *

              Ensure Singleton after serialization.

              + * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.SHORT_PREFIX_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

              ToStringStyle that does not print out the + * classname, identity hashcode, content start or field name.

              + * + *

              This is an inner class rather than using + * StandardToStringStyle to ensure its immutability.

              + */ + private static final class SimpleToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + *

              Constructor.

              + * + *

              Use the static constant rather than instantiating.

              + */ + SimpleToStringStyle() { + super(); + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + this.setUseFieldNames(false); + this.setContentStart(""); + this.setContentEnd(""); + } + + /** + *

              Ensure Singleton after serialization.

              + * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.SIMPLE_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

              ToStringStyle that outputs on multiple lines.

              + * + *

              This is an inner class rather than using + * StandardToStringStyle to ensure its immutability.

              + */ + private static final class MultiLineToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + *

              Constructor.

              + * + *

              Use the static constant rather than instantiating.

              + */ + MultiLineToStringStyle() { + super(); + this.setContentStart("["); + this.setFieldSeparator(SystemUtils.LINE_SEPARATOR + " "); + this.setFieldSeparatorAtStart(true); + this.setContentEnd(SystemUtils.LINE_SEPARATOR + "]"); + } + + /** + *

              Ensure Singleton after serialization.

              + * + * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.MULTI_LINE_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

              ToStringStyle that does not print out the classname + * and identity hashcode but prints content start and field names.

              + * + *

              This is an inner class rather than using + * StandardToStringStyle to ensure its immutability.

              + */ + private static final class NoClassNameToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + *

              Constructor.

              + * + *

              Use the static constant rather than instantiating.

              + */ + NoClassNameToStringStyle() { + super(); + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + } + + /** + *

              Ensure Singleton after serialization.

              + * + * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.NO_CLASS_NAME_STYLE; + } + + } + + // ---------------------------------------------------------------------------- + + /** + *

              + * ToStringStyle that outputs with JSON format. + *

              + * + *

              + * This is an inner class rather than using + * StandardToStringStyle to ensure its immutability. + *

              + */ + private static final class JsonToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + * The summary size text start '>'. + */ + private String FIELD_NAME_PREFIX = "\""; + + /** + *

              + * Constructor. + *

              + * + *

              + * Use the static constant rather than instantiating. + *

              + */ + JsonToStringStyle() { + super(); + + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + + this.setContentStart("{"); + this.setContentEnd("}"); + + this.setArrayStart("["); + this.setArrayEnd("]"); + + this.setFieldSeparator(","); + this.setFieldNameValueSeparator(":"); + + this.setNullText("null"); + + this.setSummaryObjectStartText("\"<"); + this.setSummaryObjectEndText(">\""); + + this.setSizeStartText("\"\""); + } + + @Override + public void append(StringBuffer buffer, String fieldName, + Object[] array, Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, long[] array, + Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, int[] array, + Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, + short[] array, Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, byte[] array, + Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, char[] array, + Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, + double[] array, Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, + float[] array, Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, + boolean[] array, Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, Object value, + Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, value, fullDetail); + } + + @Override + protected void appendDetail(StringBuffer buffer, String fieldName, Object value) { + + if (value == null) { + + appendNullText(buffer, fieldName); + return; + } + + if (value.getClass() == String.class) { + + appendValueAsString(buffer, (String)value); + return; + } + + buffer.append(value); + } + + /** + * Appends the given String in parenthesis to the given StringBuffer. + * + * @param buffer the StringBuffer to append the value to. + * @param value the value to append. + */ + private void appendValueAsString(StringBuffer buffer, String value) { + buffer.append("\"" + value + "\""); + } + + @Override + protected void appendFieldStart(StringBuffer buffer, String fieldName) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + + super.appendFieldStart(buffer, FIELD_NAME_PREFIX + fieldName + + FIELD_NAME_PREFIX); + } + + /** + *

              + * Ensure Singleton after serialization. + *

              + * + * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.JSON_STYLE; + } + + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/package-info.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/package-info.java new file mode 100644 index 000000000..f0326273b --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/builder/package-info.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *

              Assists in creating consistent {@code equals(Object)}, {@code toString()}, {@code hashCode()}, and {@code compareTo(Object)} methods. + * These classes are not thread-safe.

              + * + *

              When you write a {@link java.lang.Object#hashCode() hashCode()}, do you check Bloch's Effective Java? No? + * You just hack in a quick number? + * Well {@link com.fr.third.org.apache.commons.lang3.builder.HashCodeBuilder} will save your day. + * It, and its buddies ({@link com.fr.third.org.apache.commons.lang3.builder.EqualsBuilder}, {@link com.fr.third.org.apache.commons.lang3.builder.CompareToBuilder}, {@link com.fr.third.org.apache.commons.lang3.builder.ToStringBuilder}), take care of the nasty bits while you focus on the important bits, like which fields will go into making up the hashcode.

              + * + * @see java.lang.Object#equals(Object) + * @see java.lang.Object#toString() + * @see java.lang.Object#hashCode() + * @see java.lang.Comparable#compareTo(Object) + * + * @since 1.0 + * @version $Id: package-info.java 1559146 2014-01-17 15:23:19Z britter $ + */ +package com.fr.third.org.apache.commons.lang3.builder; diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/AtomicInitializer.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/AtomicInitializer.java new file mode 100644 index 000000000..904b1c5f4 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/AtomicInitializer.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +import java.util.concurrent.atomic.AtomicReference; + +/** + *

              + * A specialized implementation of the {@code ConcurrentInitializer} interface + * based on an {@link AtomicReference} variable. + *

              + *

              + * This class maintains a member field of type {@code AtomicReference}. It + * implements the following algorithm to create and initialize an object in its + * {@link #get()} method: + *

              + *
                + *
              • First it is checked whether the {@code AtomicReference} variable contains + * already a value. If this is the case, the value is directly returned.
              • + *
              • Otherwise the {@link #initialize()} method is called. This method must be + * defined in concrete subclasses to actually create the managed object.
              • + *
              • After the object was created by {@link #initialize()} it is checked + * whether the {@code AtomicReference} variable is still undefined. This has to + * be done because in the meantime another thread may have initialized the + * object. If the reference is still empty, the newly created object is stored + * in it and returned by this method.
              • + *
              • Otherwise the value stored in the {@code AtomicReference} is returned.
              • + *
              + *

              + * Because atomic variables are used this class does not need any + * synchronization. So there is no danger of deadlock, and access to the managed + * object is efficient. However, if multiple threads access the {@code + * AtomicInitializer} object before it has been initialized almost at the same + * time, it can happen that {@link #initialize()} is called multiple times. The + * algorithm outlined above guarantees that {@link #get()} always returns the + * same object though. + *

              + *

              + * Compared with the {@link LazyInitializer} class, this class can be more + * efficient because it does not need synchronization. The drawback is that the + * {@link #initialize()} method can be called multiple times which may be + * problematic if the creation of the managed object is expensive. As a rule of + * thumb this initializer implementation is preferable if there are not too many + * threads involved and the probability that multiple threads access an + * uninitialized object is small. If there is high parallelism, + * {@link LazyInitializer} is more appropriate. + *

              + * + * @since 3.0 + * @version $Id: AtomicInitializer.java 1583482 2014-03-31 22:54:57Z niallp $ + * @param the type of the object managed by this initializer class + */ +public abstract class AtomicInitializer implements ConcurrentInitializer { + /** Holds the reference to the managed object. */ + private final AtomicReference reference = new AtomicReference(); + + /** + * Returns the object managed by this initializer. The object is created if + * it is not available yet and stored internally. This method always returns + * the same object. + * + * @return the object created by this {@code AtomicInitializer} + * @throws ConcurrentException if an error occurred during initialization of + * the object + */ + @Override + public T get() throws ConcurrentException { + T result = reference.get(); + + if (result == null) { + result = initialize(); + if (!reference.compareAndSet(null, result)) { + // another thread has initialized the reference + result = reference.get(); + } + } + + return result; + } + + /** + * Creates and initializes the object managed by this {@code + * AtomicInitializer}. This method is called by {@link #get()} when the + * managed object is not available yet. An implementation can focus on the + * creation of the object. No synchronization is needed, as this is already + * handled by {@code get()}. As stated by the class comment, it is possible + * that this method is called multiple times. + * + * @return the managed data object + * @throws ConcurrentException if an error occurs during object creation + */ + protected abstract T initialize() throws ConcurrentException; +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java new file mode 100644 index 000000000..d5f15f1aa --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +import java.util.concurrent.atomic.AtomicReference; + +/** + *

              + * A specialized {@code ConcurrentInitializer} implementation which is similar + * to {@link AtomicInitializer}, but ensures that the {@link #initialize()} + * method is called only once. + *

              + *

              + * As {@link AtomicInitializer} this class is based on atomic variables, so it + * can create an object under concurrent access without synchronization. + * However, it implements an additional check to guarantee that the + * {@link #initialize()} method which actually creates the object cannot be + * called multiple times. + *

              + *

              + * Because of this additional check this implementation is slightly less + * efficient than {@link AtomicInitializer}, but if the object creation in the + * {@code initialize()} method is expensive or if multiple invocations of + * {@code initialize()} are problematic, it is the better alternative. + *

              + *

              + * From its semantics this class has the same properties as + * {@link LazyInitializer}. It is a "save" implementation of the lazy + * initializer pattern. Comparing both classes in terms of efficiency is + * difficult because which one is faster depends on multiple factors. Because + * {@code AtomicSafeInitializer} does not use synchronization at all it probably + * outruns {@link LazyInitializer}, at least under low or moderate concurrent + * access. Developers should run their own benchmarks on the expected target + * platform to decide which implementation is suitable for their specific use + * case. + *

              + * + * @since 3.0 + * @version $Id: AtomicSafeInitializer.java 1662379 2015-02-26 08:13:58Z britter $ + * @param the type of the object managed by this initializer class + */ +public abstract class AtomicSafeInitializer implements + ConcurrentInitializer { + /** A guard which ensures that initialize() is called only once. */ + private final AtomicReference> factory = + new AtomicReference>(); + + /** Holds the reference to the managed object. */ + private final AtomicReference reference = new AtomicReference(); + + /** + * Get (and initialize, if not initialized yet) the required object + * + * @return lazily initialized object + * @throws ConcurrentException if the initialization of the object causes an + * exception + */ + @Override + public final T get() throws ConcurrentException { + T result; + + while ((result = reference.get()) == null) { + if (factory.compareAndSet(null, this)) { + reference.set(initialize()); + } + } + + return result; + } + + /** + * Creates and initializes the object managed by this + * {@code AtomicInitializer}. This method is called by {@link #get()} when + * the managed object is not available yet. An implementation can focus on + * the creation of the object. No synchronization is needed, as this is + * already handled by {@code get()}. This method is guaranteed to be called + * only once. + * + * @return the managed data object + * @throws ConcurrentException if an error occurs during object creation + */ + protected abstract T initialize() throws ConcurrentException; +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/BackgroundInitializer.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/BackgroundInitializer.java new file mode 100644 index 000000000..0c0702ed7 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/BackgroundInitializer.java @@ -0,0 +1,335 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + *

              + * A class that allows complex initialization operations in a background task. + *

              + *

              + * Applications often have to do some expensive initialization steps when they + * are started, e.g. constructing a connection to a database, reading a + * configuration file, etc. Doing these things in parallel can enhance + * performance as the CPU load can be improved. However, when access to the + * resources initialized in a background thread is actually required, + * synchronization has to be performed to ensure that their initialization is + * complete. + *

              + *

              + * This abstract base class provides support for this use case. A concrete + * subclass must implement the {@link #initialize()} method. Here an arbitrary + * initialization can be implemented, and a result object can be returned. With + * this method in place the basic usage of this class is as follows (where + * {@code MyBackgroundInitializer} is a concrete subclass): + *

              + * + *
              + * MyBackgroundInitializer initializer = new MyBackgroundInitializer();
              + * initializer.start();
              + * // Now do some other things. Initialization runs in a parallel thread
              + * ...
              + * // Wait for the end of initialization and access the result object
              + * Object result = initializer.get();
              + * 
              + * + *

              + * After the construction of a {@code BackgroundInitializer} object its + * {@link #start()} method has to be called. This starts the background + * processing. The application can now continue to do other things. When it + * needs access to the object produced by the {@code BackgroundInitializer} it + * calls its {@link #get()} method. If initialization is already complete, + * {@link #get()} returns the result object immediately. Otherwise it blocks + * until the result object is fully constructed. + *

              + *

              + * {@code BackgroundInitializer} is a thin wrapper around a {@code Future} + * object and uses an {@code ExecutorService} for running the background + * initialization task. It is possible to pass in an {@code ExecutorService} at + * construction time or set one using {@code setExternalExecutor()} before + * {@code start()} was called. Then this object is used to spawn the background + * task. If no {@code ExecutorService} has been provided, {@code + * BackgroundInitializer} creates a temporary {@code ExecutorService} and + * destroys it when initialization is complete. + *

              + *

              + * The methods provided by {@code BackgroundInitializer} provide for minimal + * interaction with the wrapped {@code Future} object. It is also possible to + * obtain the {@code Future} object directly. Then the enhanced functionality + * offered by {@code Future} can be used, e.g. to check whether the background + * operation is complete or to cancel the operation. + *

              + * + * @since 3.0 + * @version $Id: BackgroundInitializer.java 1583482 2014-03-31 22:54:57Z niallp $ + * @param the type of the object managed by this initializer class + */ +public abstract class BackgroundInitializer implements + ConcurrentInitializer { + /** The external executor service for executing tasks. */ + private ExecutorService externalExecutor; // @GuardedBy("this") + + /** A reference to the executor service that is actually used. */ + private ExecutorService executor; // @GuardedBy("this") + + /** Stores the handle to the background task. */ + private Future future; // @GuardedBy("this") + + /** + * Creates a new instance of {@code BackgroundInitializer}. No external + * {@code ExecutorService} is used. + */ + protected BackgroundInitializer() { + this(null); + } + + /** + * Creates a new instance of {@code BackgroundInitializer} and initializes + * it with the given {@code ExecutorService}. If the {@code ExecutorService} + * is not null, the background task for initializing this object will be + * scheduled at this service. Otherwise a new temporary {@code + * ExecutorService} is created. + * + * @param exec an external {@code ExecutorService} to be used for task + * execution + */ + protected BackgroundInitializer(final ExecutorService exec) { + setExternalExecutor(exec); + } + + /** + * Returns the external {@code ExecutorService} to be used by this class. + * + * @return the {@code ExecutorService} + */ + public final synchronized ExecutorService getExternalExecutor() { + return externalExecutor; + } + + /** + * Returns a flag whether this {@code BackgroundInitializer} has already + * been started. + * + * @return a flag whether the {@link #start()} method has already been + * called + */ + public synchronized boolean isStarted() { + return future != null; + } + + /** + * Sets an {@code ExecutorService} to be used by this class. The {@code + * ExecutorService} passed to this method is used for executing the + * background task. Thus it is possible to re-use an already existing + * {@code ExecutorService} or to use a specially configured one. If no + * {@code ExecutorService} is set, this instance creates a temporary one and + * destroys it after background initialization is complete. Note that this + * method must be called before {@link #start()}; otherwise an exception is + * thrown. + * + * @param externalExecutor the {@code ExecutorService} to be used + * @throws IllegalStateException if this initializer has already been + * started + */ + public final synchronized void setExternalExecutor( + final ExecutorService externalExecutor) { + if (isStarted()) { + throw new IllegalStateException( + "Cannot set ExecutorService after start()!"); + } + + this.externalExecutor = externalExecutor; + } + + /** + * Starts the background initialization. With this method the initializer + * becomes active and invokes the {@link #initialize()} method in a + * background task. A {@code BackgroundInitializer} can be started exactly + * once. The return value of this method determines whether the start was + * successful: only the first invocation of this method returns true, + * following invocations will return false. + * + * @return a flag whether the initializer could be started successfully + */ + public synchronized boolean start() { + // Not yet started? + if (!isStarted()) { + + // Determine the executor to use and whether a temporary one has to + // be created + ExecutorService tempExec; + executor = getExternalExecutor(); + if (executor == null) { + executor = tempExec = createExecutor(); + } else { + tempExec = null; + } + + future = executor.submit(createTask(tempExec)); + + return true; + } + + return false; + } + + /** + * Returns the result of the background initialization. This method blocks + * until initialization is complete. If the background processing caused a + * runtime exception, it is directly thrown by this method. Checked + * exceptions, including {@code InterruptedException} are wrapped in a + * {@link ConcurrentException}. Calling this method before {@link #start()} + * was called causes an {@code IllegalStateException} exception to be + * thrown. + * + * @return the object produced by this initializer + * @throws ConcurrentException if a checked exception occurred during + * background processing + * @throws IllegalStateException if {@link #start()} has not been called + */ + @Override + public T get() throws ConcurrentException { + try { + return getFuture().get(); + } catch (final ExecutionException execex) { + ConcurrentUtils.handleCause(execex); + return null; // should not be reached + } catch (final InterruptedException iex) { + // reset interrupted state + Thread.currentThread().interrupt(); + throw new ConcurrentException(iex); + } + } + + /** + * Returns the {@code Future} object that was created when {@link #start()} + * was called. Therefore this method can only be called after {@code + * start()}. + * + * @return the {@code Future} object wrapped by this initializer + * @throws IllegalStateException if {@link #start()} has not been called + */ + public synchronized Future getFuture() { + if (future == null) { + throw new IllegalStateException("start() must be called first!"); + } + + return future; + } + + /** + * Returns the {@code ExecutorService} that is actually used for executing + * the background task. This method can be called after {@link #start()} + * (before {@code start()} it returns null). If an external executor + * was set, this is also the active executor. Otherwise this method returns + * the temporary executor that was created by this object. + * + * @return the {@code ExecutorService} for executing the background task + */ + protected synchronized final ExecutorService getActiveExecutor() { + return executor; + } + + /** + * Returns the number of background tasks to be created for this + * initializer. This information is evaluated when a temporary {@code + * ExecutorService} is created. This base implementation returns 1. Derived + * classes that do more complex background processing can override it. This + * method is called from a synchronized block by the {@link #start()} + * method. Therefore overriding methods should be careful with obtaining + * other locks and return as fast as possible. + * + * @return the number of background tasks required by this initializer + */ + protected int getTaskCount() { + return 1; + } + + /** + * Performs the initialization. This method is called in a background task + * when this {@code BackgroundInitializer} is started. It must be + * implemented by a concrete subclass. An implementation is free to perform + * arbitrary initialization. The object returned by this method can be + * queried using the {@link #get()} method. + * + * @return a result object + * @throws Exception if an error occurs + */ + protected abstract T initialize() throws Exception; + + /** + * Creates a task for the background initialization. The {@code Callable} + * object returned by this method is passed to the {@code ExecutorService}. + * This implementation returns a task that invokes the {@link #initialize()} + * method. If a temporary {@code ExecutorService} is used, it is destroyed + * at the end of the task. + * + * @param execDestroy the {@code ExecutorService} to be destroyed by the + * task + * @return a task for the background initialization + */ + private Callable createTask(final ExecutorService execDestroy) { + return new InitializationTask(execDestroy); + } + + /** + * Creates the {@code ExecutorService} to be used. This method is called if + * no {@code ExecutorService} was provided at construction time. + * + * @return the {@code ExecutorService} to be used + */ + private ExecutorService createExecutor() { + return Executors.newFixedThreadPool(getTaskCount()); + } + + private class InitializationTask implements Callable { + /** Stores the executor service to be destroyed at the end. */ + private final ExecutorService execFinally; + + /** + * Creates a new instance of {@code InitializationTask} and initializes + * it with the {@code ExecutorService} to be destroyed at the end. + * + * @param exec the {@code ExecutorService} + */ + public InitializationTask(final ExecutorService exec) { + execFinally = exec; + } + + /** + * Initiates initialization and returns the result. + * + * @return the result object + * @throws Exception if an error occurs + */ + @Override + public T call() throws Exception { + try { + return initialize(); + } finally { + if (execFinally != null) { + execFinally.shutdown(); + } + } + } + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/BasicThreadFactory.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/BasicThreadFactory.java new file mode 100644 index 000000000..69a6a2735 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/BasicThreadFactory.java @@ -0,0 +1,383 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; + +/** + *

              + * An implementation of the {@code ThreadFactory} interface that provides some + * configuration options for the threads it creates. + *

              + *

              + * A {@code ThreadFactory} is used for instance by an {@code ExecutorService} to + * create the threads it uses for executing tasks. In many cases users do not + * have to care about a {@code ThreadFactory} because the default one used by an + * {@code ExecutorService} will do. However, if there are special requirements + * for the threads, a custom {@code ThreadFactory} has to be created. + *

              + *

              + * This class provides some frequently needed configuration options for the + * threads it creates. These are the following: + *

              + *
                + *
              • A name pattern for the threads created by this factory can be specified. + * This is often useful if an application uses multiple executor services for + * different purposes. If the names of the threads used by these services have + * meaningful names, log output or exception traces can be much easier to read. + * Naming patterns are format strings as used by the {@code + * String.format()} method. The string can contain the place holder {@code %d} + * which will be replaced by the number of the current thread ({@code + * ThreadFactoryImpl} keeps a counter of the threads it has already created). + * For instance, the naming pattern {@code "My %d. worker thread"} will result + * in thread names like {@code "My 1. worker thread"}, {@code + * "My 2. worker thread"} and so on.
              • + *
              • A flag whether the threads created by this factory should be daemon + * threads. This can impact the exit behavior of the current Java application + * because the JVM shuts down if there are only daemon threads running.
              • + *
              • The priority of the thread. Here an integer value can be provided. The + * {@code java.lang.Thread} class defines constants for valid ranges of priority + * values.
              • + *
              • The {@code UncaughtExceptionHandler} for the thread. This handler is + * called if an uncaught exception occurs within the thread.
              • + *
              + *

              + * {@code BasicThreadFactory} wraps another thread factory which actually + * creates new threads. The configuration options are set on the threads created + * by the wrapped thread factory. On construction time the factory to be wrapped + * can be specified. If none is provided, a default {@code ThreadFactory} is + * used. + *

              + *

              + * Instances of {@code BasicThreadFactory} are not created directly, but the + * nested {@code Builder} class is used for this purpose. Using the builder only + * the configuration options an application is interested in need to be set. The + * following example shows how a {@code BasicThreadFactory} is created and + * installed in an {@code ExecutorService}: + *

              + * + *
              + * // Create a factory that produces daemon threads with a naming pattern and
              + * // a priority
              + * BasicThreadFactory factory = new BasicThreadFactory.Builder()
              + *     .namingPattern("workerthread-%d")
              + *     .daemon(true)
              + *     .priority(Thread.MAX_PRIORITY)
              + *     .build();
              + * // Create an executor service for single-threaded execution
              + * ExecutorService exec = Executors.newSingleThreadExecutor(factory);
              + * 
              + * + * @since 3.0 + * @version $Id: BasicThreadFactory.java 1583482 2014-03-31 22:54:57Z niallp $ + */ +public class BasicThreadFactory implements ThreadFactory { + /** A counter for the threads created by this factory. */ + private final AtomicLong threadCounter; + + /** Stores the wrapped factory. */ + private final ThreadFactory wrappedFactory; + + /** Stores the uncaught exception handler. */ + private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler; + + /** Stores the naming pattern for newly created threads. */ + private final String namingPattern; + + /** Stores the priority. */ + private final Integer priority; + + /** Stores the daemon status flag. */ + private final Boolean daemonFlag; + + /** + * Creates a new instance of {@code ThreadFactoryImpl} and configures it + * from the specified {@code Builder} object. + * + * @param builder the {@code Builder} object + */ + private BasicThreadFactory(final Builder builder) { + if (builder.wrappedFactory == null) { + wrappedFactory = Executors.defaultThreadFactory(); + } else { + wrappedFactory = builder.wrappedFactory; + } + + namingPattern = builder.namingPattern; + priority = builder.priority; + daemonFlag = builder.daemonFlag; + uncaughtExceptionHandler = builder.exceptionHandler; + + threadCounter = new AtomicLong(); + } + + /** + * Returns the wrapped {@code ThreadFactory}. This factory is used for + * actually creating threads. This method never returns null. If no + * {@code ThreadFactory} was passed when this object was created, a default + * thread factory is returned. + * + * @return the wrapped {@code ThreadFactory} + */ + public final ThreadFactory getWrappedFactory() { + return wrappedFactory; + } + + /** + * Returns the naming pattern for naming newly created threads. Result can + * be null if no naming pattern was provided. + * + * @return the naming pattern + */ + public final String getNamingPattern() { + return namingPattern; + } + + /** + * Returns the daemon flag. This flag determines whether newly created + * threads should be daemon threads. If true, this factory object + * calls {@code setDaemon(true)} on the newly created threads. Result can be + * null if no daemon flag was provided at creation time. + * + * @return the daemon flag + */ + public final Boolean getDaemonFlag() { + return daemonFlag; + } + + /** + * Returns the priority of the threads created by this factory. Result can + * be null if no priority was specified. + * + * @return the priority for newly created threads + */ + public final Integer getPriority() { + return priority; + } + + /** + * Returns the {@code UncaughtExceptionHandler} for the threads created by + * this factory. Result can be null if no handler was provided. + * + * @return the {@code UncaughtExceptionHandler} + */ + public final Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() { + return uncaughtExceptionHandler; + } + + /** + * Returns the number of threads this factory has already created. This + * class maintains an internal counter that is incremented each time the + * {@link #newThread(Runnable)} method is invoked. + * + * @return the number of threads created by this factory + */ + public long getThreadCount() { + return threadCounter.get(); + } + + /** + * Creates a new thread. This implementation delegates to the wrapped + * factory for creating the thread. Then, on the newly created thread the + * corresponding configuration options are set. + * + * @param r the {@code Runnable} to be executed by the new thread + * @return the newly created thread + */ + @Override + public Thread newThread(final Runnable r) { + final Thread t = getWrappedFactory().newThread(r); + initializeThread(t); + + return t; + } + + /** + * Initializes the specified thread. This method is called by + * {@link #newThread(Runnable)} after a new thread has been obtained from + * the wrapped thread factory. It initializes the thread according to the + * options set for this factory. + * + * @param t the thread to be initialized + */ + private void initializeThread(final Thread t) { + + if (getNamingPattern() != null) { + final Long count = Long.valueOf(threadCounter.incrementAndGet()); + t.setName(String.format(getNamingPattern(), count)); + } + + if (getUncaughtExceptionHandler() != null) { + t.setUncaughtExceptionHandler(getUncaughtExceptionHandler()); + } + + if (getPriority() != null) { + t.setPriority(getPriority().intValue()); + } + + if (getDaemonFlag() != null) { + t.setDaemon(getDaemonFlag().booleanValue()); + } + } + + /** + *

              + * A builder class for creating instances of {@code + * BasicThreadFactory}. + *

              + *

              + * Using this builder class instances of {@code BasicThreadFactory} can be + * created and initialized. The class provides methods that correspond to + * the configuration options supported by {@code BasicThreadFactory}. Method + * chaining is supported. Refer to the documentation of {@code + * BasicThreadFactory} for a usage example. + *

              + * + * @version $Id: BasicThreadFactory.java 1583482 2014-03-31 22:54:57Z niallp $ + */ + public static class Builder + implements com.fr.third.org.apache.commons.lang3.builder.Builder { + + /** The wrapped factory. */ + private ThreadFactory wrappedFactory; + + /** The uncaught exception handler. */ + private Thread.UncaughtExceptionHandler exceptionHandler; + + /** The naming pattern. */ + private String namingPattern; + + /** The priority. */ + private Integer priority; + + /** The daemon flag. */ + private Boolean daemonFlag; + + /** + * Sets the {@code ThreadFactory} to be wrapped by the new {@code + * BasicThreadFactory}. + * + * @param factory the wrapped {@code ThreadFactory} (must not be + * null) + * @return a reference to this {@code Builder} + * @throws NullPointerException if the passed in {@code ThreadFactory} + * is null + */ + public Builder wrappedFactory(final ThreadFactory factory) { + if (factory == null) { + throw new NullPointerException( + "Wrapped ThreadFactory must not be null!"); + } + + wrappedFactory = factory; + return this; + } + + /** + * Sets the naming pattern to be used by the new {@code + * BasicThreadFactory}. + * + * @param pattern the naming pattern (must not be null) + * @return a reference to this {@code Builder} + * @throws NullPointerException if the naming pattern is null + */ + public Builder namingPattern(final String pattern) { + if (pattern == null) { + throw new NullPointerException( + "Naming pattern must not be null!"); + } + + namingPattern = pattern; + return this; + } + + /** + * Sets the daemon flag for the new {@code BasicThreadFactory}. If this + * flag is set to true the new thread factory will create daemon + * threads. + * + * @param f the value of the daemon flag + * @return a reference to this {@code Builder} + */ + public Builder daemon(final boolean f) { + daemonFlag = Boolean.valueOf(f); + return this; + } + + /** + * Sets the priority for the threads created by the new {@code + * BasicThreadFactory}. + * + * @param prio the priority + * @return a reference to this {@code Builder} + */ + public Builder priority(final int prio) { + priority = Integer.valueOf(prio); + return this; + } + + /** + * Sets the uncaught exception handler for the threads created by the + * new {@code BasicThreadFactory}. + * + * @param handler the {@code UncaughtExceptionHandler} (must not be + * null) + * @return a reference to this {@code Builder} + * @throws NullPointerException if the exception handler is null + */ + public Builder uncaughtExceptionHandler( + final Thread.UncaughtExceptionHandler handler) { + if (handler == null) { + throw new NullPointerException( + "Uncaught exception handler must not be null!"); + } + + exceptionHandler = handler; + return this; + } + + /** + * Resets this builder. All configuration options are set to default + * values. Note: If the {@link #build()} method was called, it is not + * necessary to call {@code reset()} explicitly because this is done + * automatically. + */ + public void reset() { + wrappedFactory = null; + exceptionHandler = null; + namingPattern = null; + priority = null; + daemonFlag = null; + } + + /** + * Creates a new {@code BasicThreadFactory} with all configuration + * options that have been specified by calling methods on this builder. + * After creating the factory {@link #reset()} is called. + * + * @return the new {@code BasicThreadFactory} + */ + @Override + public BasicThreadFactory build() { + final BasicThreadFactory factory = new BasicThreadFactory(this); + reset(); + return factory; + } + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java new file mode 100644 index 000000000..0fa261c7e --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; + +/** + *

              + * A specialized {@link BackgroundInitializer} implementation that wraps a + * {@code Callable} object. + *

              + *

              + * An instance of this class is initialized with a {@code Callable} object when + * it is constructed. The implementation of the {@link #initialize()} method + * defined in the super class delegates to this {@code Callable} so that the + * {@code Callable} is executed in the background thread. + *

              + *

              + * The {@code java.util.concurrent.Callable} interface is a standard mechanism + * of the JDK to define tasks to be executed by another thread. The {@code + * CallableBackgroundInitializer} class allows combining this standard interface + * with the background initializer API. + *

              + *

              + * Usage of this class is very similar to the default usage pattern of the + * {@link BackgroundInitializer} class: Just create an instance and provide the + * {@code Callable} object to be executed, then call the initializer's + * {@link #start()} method. This causes the {@code Callable} to be executed in + * another thread. When the results of the {@code Callable} are needed the + * initializer's {@link #get()} method can be called (which may block until + * background execution is complete). The following code fragment shows a + * typical usage example: + *

              + * + *
              + * // a Callable that performs a complex computation
              + * Callable<Integer> computationCallable = new MyComputationCallable();
              + * // setup the background initializer
              + * CallableBackgroundInitializer<Integer> initializer =
              + *     new CallableBackgroundInitializer(computationCallable);
              + * initializer.start();
              + * // Now do some other things. Initialization runs in a parallel thread
              + * ...
              + * // Wait for the end of initialization and access the result
              + * Integer result = initializer.get();
              + * 
              + * + * + * @since 3.0 + * @version $Id: CallableBackgroundInitializer.java 1583482 2014-03-31 22:54:57Z niallp $ + * @param the type of the object managed by this initializer class + */ +public class CallableBackgroundInitializer extends BackgroundInitializer { + /** The Callable to be executed. */ + private final Callable callable; + + /** + * Creates a new instance of {@code CallableBackgroundInitializer} and sets + * the {@code Callable} to be executed in a background thread. + * + * @param call the {@code Callable} (must not be null) + * @throws IllegalArgumentException if the {@code Callable} is null + */ + public CallableBackgroundInitializer(final Callable call) { + checkCallable(call); + callable = call; + } + + /** + * Creates a new instance of {@code CallableBackgroundInitializer} and + * initializes it with the {@code Callable} to be executed in a background + * thread and the {@code ExecutorService} for managing the background + * execution. + * + * @param call the {@code Callable} (must not be null) + * @param exec an external {@code ExecutorService} to be used for task + * execution + * @throws IllegalArgumentException if the {@code Callable} is null + */ + public CallableBackgroundInitializer(final Callable call, final ExecutorService exec) { + super(exec); + checkCallable(call); + callable = call; + } + + /** + * Performs initialization in a background thread. This implementation + * delegates to the {@code Callable} passed at construction time of this + * object. + * + * @return the result of the initialization + * @throws Exception if an error occurs + */ + @Override + protected T initialize() throws Exception { + return callable.call(); + } + + /** + * Tests the passed in {@code Callable} and throws an exception if it is + * undefined. + * + * @param call the object to check + * @throws IllegalArgumentException if the {@code Callable} is null + */ + private void checkCallable(final Callable call) { + if (call == null) { + throw new IllegalArgumentException("Callable must not be null!"); + } + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentException.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentException.java new file mode 100644 index 000000000..2d6e1f46f --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentException.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +/** + *

              + * An exception class used for reporting error conditions related to accessing + * data of background tasks. + *

              + *

              + * The purpose of this exception class is analogous to the default JDK exception + * class {@link java.util.concurrent.ExecutionException}, i.e. it wraps an + * exception that occurred during the execution of a task. However, in contrast + * to {@code ExecutionException}, it wraps only checked exceptions. Runtime + * exceptions are thrown directly. + *

              + * + * @since 3.0 + * @version $Id: ConcurrentException.java 1436768 2013-01-22 07:07:42Z ggregory $ + */ +public class ConcurrentException extends Exception { + /** + * The serial version UID. + */ + private static final long serialVersionUID = 6622707671812226130L; + + /** + * Creates a new, uninitialized instance of {@code ConcurrentException}. + */ + protected ConcurrentException() { + super(); + } + + /** + * Creates a new instance of {@code ConcurrentException} and initializes it + * with the given cause. + * + * @param cause the cause of this exception + * @throws IllegalArgumentException if the cause is not a checked exception + */ + public ConcurrentException(final Throwable cause) { + super(ConcurrentUtils.checkedException(cause)); + } + + /** + * Creates a new instance of {@code ConcurrentException} and initializes it + * with the given message and cause. + * + * @param msg the error message + * @param cause the cause of this exception + * @throws IllegalArgumentException if the cause is not a checked exception + */ + public ConcurrentException(final String msg, final Throwable cause) { + super(msg, ConcurrentUtils.checkedException(cause)); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentInitializer.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentInitializer.java new file mode 100644 index 000000000..d2a0ce4fa --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentInitializer.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +/** + *

              + * Definition of an interface for the thread-safe initialization of objects. + *

              + *

              + * The idea behind this interface is to provide access to an object in a + * thread-safe manner. A {@code ConcurrentInitializer} can be passed to multiple + * threads which can all access the object produced by the initializer. Through + * the {@link #get()} method the object can be queried. + *

              + *

              + * Concrete implementations of this interface will use different strategies for + * the creation of the managed object, e.g. lazy initialization or + * initialization in a background thread. This is completely transparent to + * client code, so it is possible to change the initialization strategy without + * affecting clients. + *

              + * + * @since 3.0 + * @version $Id: ConcurrentInitializer.java 1088899 2011-04-05 05:31:27Z bayard $ + * @param the type of the object managed by this initializer class + */ +public interface ConcurrentInitializer { + /** + * Returns the fully initialized object produced by this {@code + * ConcurrentInitializer}. A concrete implementation here returns the + * results of the initialization process. This method may block until + * results are available. Typically, once created the result object is + * always the same. + * + * @return the object created by this {@code ConcurrentException} + * @throws ConcurrentException if an error occurred during initialization of + * the object + */ + T get() throws ConcurrentException; +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java new file mode 100644 index 000000000..a14b6799d --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +/** + *

              + * An exception class used for reporting runtime error conditions related to + * accessing data of background tasks. + *

              + *

              + * This class is an analogon of the {@link ConcurrentException} exception class. + * However, it is a runtime exception and thus does not need explicit catch + * clauses. Some methods of {@link ConcurrentUtils} throw {@code + * ConcurrentRuntimeException} exceptions rather than + * {@link ConcurrentException} exceptions. They can be used by client code that + * does not want to be bothered with checked exceptions. + *

              + * + * @since 3.0 + * @version $Id: ConcurrentRuntimeException.java 1436768 2013-01-22 07:07:42Z ggregory $ + */ +public class ConcurrentRuntimeException extends RuntimeException { + /** + * The serial version UID. + */ + private static final long serialVersionUID = -6582182735562919670L; + + /** + * Creates a new, uninitialized instance of {@code + * ConcurrentRuntimeException}. + */ + protected ConcurrentRuntimeException() { + super(); + } + + /** + * Creates a new instance of {@code ConcurrentRuntimeException} and + * initializes it with the given cause. + * + * @param cause the cause of this exception + * @throws IllegalArgumentException if the cause is not a checked exception + */ + public ConcurrentRuntimeException(final Throwable cause) { + super(ConcurrentUtils.checkedException(cause)); + } + + /** + * Creates a new instance of {@code ConcurrentRuntimeException} and + * initializes it with the given message and cause. + * + * @param msg the error message + * @param cause the cause of this exception + * @throws IllegalArgumentException if the cause is not a checked exception + */ + public ConcurrentRuntimeException(final String msg, final Throwable cause) { + super(msg, ConcurrentUtils.checkedException(cause)); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentUtils.java new file mode 100644 index 000000000..6fe4abcbc --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConcurrentUtils.java @@ -0,0 +1,393 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import com.fr.third.org.apache.commons.lang3.Validate; + +/** + *

              + * An utility class providing functionality related to the {@code + * java.util.concurrent} package. + *

              + * + * @since 3.0 + * @version $Id: ConcurrentUtils.java 1593668 2014-05-10 05:58:17Z djones $ + */ +public class ConcurrentUtils { + + /** + * Private constructor so that no instances can be created. This class + * contains only static utility methods. + */ + private ConcurrentUtils() { + } + + /** + * Inspects the cause of the specified {@code ExecutionException} and + * creates a {@code ConcurrentException} with the checked cause if + * necessary. This method performs the following checks on the cause of the + * passed in exception: + *
                + *
              • If the passed in exception is null or the cause is + * null, this method returns null.
              • + *
              • If the cause is a runtime exception, it is directly thrown.
              • + *
              • If the cause is an error, it is directly thrown, too.
              • + *
              • In any other case the cause is a checked exception. The method then + * creates a {@link ConcurrentException}, initializes it with the cause, and + * returns it.
              • + *
              + * + * @param ex the exception to be processed + * @return a {@code ConcurrentException} with the checked cause + */ + public static ConcurrentException extractCause(final ExecutionException ex) { + if (ex == null || ex.getCause() == null) { + return null; + } + + throwCause(ex); + return new ConcurrentException(ex.getMessage(), ex.getCause()); + } + + /** + * Inspects the cause of the specified {@code ExecutionException} and + * creates a {@code ConcurrentRuntimeException} with the checked cause if + * necessary. This method works exactly like + * {@link #extractCause(ExecutionException)}. The only difference is that + * the cause of the specified {@code ExecutionException} is extracted as a + * runtime exception. This is an alternative for client code that does not + * want to deal with checked exceptions. + * + * @param ex the exception to be processed + * @return a {@code ConcurrentRuntimeException} with the checked cause + */ + public static ConcurrentRuntimeException extractCauseUnchecked( + final ExecutionException ex) { + if (ex == null || ex.getCause() == null) { + return null; + } + + throwCause(ex); + return new ConcurrentRuntimeException(ex.getMessage(), ex.getCause()); + } + + /** + * Handles the specified {@code ExecutionException}. This method calls + * {@link #extractCause(ExecutionException)} for obtaining the cause of the + * exception - which might already cause an unchecked exception or an error + * being thrown. If the cause is a checked exception however, it is wrapped + * in a {@code ConcurrentException}, which is thrown. If the passed in + * exception is null or has no cause, the method simply returns + * without throwing an exception. + * + * @param ex the exception to be handled + * @throws ConcurrentException if the cause of the {@code + * ExecutionException} is a checked exception + */ + public static void handleCause(final ExecutionException ex) + throws ConcurrentException { + final ConcurrentException cex = extractCause(ex); + + if (cex != null) { + throw cex; + } + } + + /** + * Handles the specified {@code ExecutionException} and transforms it into a + * runtime exception. This method works exactly like + * {@link #handleCause(ExecutionException)}, but instead of a + * {@link ConcurrentException} it throws a + * {@link ConcurrentRuntimeException}. This is an alternative for client + * code that does not want to deal with checked exceptions. + * + * @param ex the exception to be handled + * @throws ConcurrentRuntimeException if the cause of the {@code + * ExecutionException} is a checked exception; this exception is then + * wrapped in the thrown runtime exception + */ + public static void handleCauseUnchecked(final ExecutionException ex) { + final ConcurrentRuntimeException crex = extractCauseUnchecked(ex); + + if (crex != null) { + throw crex; + } + } + + /** + * Tests whether the specified {@code Throwable} is a checked exception. If + * not, an exception is thrown. + * + * @param ex the {@code Throwable} to check + * @return a flag whether the passed in exception is a checked exception + * @throws IllegalArgumentException if the {@code Throwable} is not a + * checked exception + */ + static Throwable checkedException(final Throwable ex) { + Validate.isTrue(ex != null && !(ex instanceof RuntimeException) + && !(ex instanceof Error), "Not a checked exception: " + ex); + + return ex; + } + + /** + * Tests whether the cause of the specified {@code ExecutionException} + * should be thrown and does it if necessary. + * + * @param ex the exception in question + */ + private static void throwCause(final ExecutionException ex) { + if (ex.getCause() instanceof RuntimeException) { + throw (RuntimeException) ex.getCause(); + } + + if (ex.getCause() instanceof Error) { + throw (Error) ex.getCause(); + } + } + + //----------------------------------------------------------------------- + /** + * Invokes the specified {@code ConcurrentInitializer} and returns the + * object produced by the initializer. This method just invokes the {@code + * get()} method of the given {@code ConcurrentInitializer}. It is + * null-safe: if the argument is null, result is also + * null. + * + * @param the type of the object produced by the initializer + * @param initializer the {@code ConcurrentInitializer} to be invoked + * @return the object managed by the {@code ConcurrentInitializer} + * @throws ConcurrentException if the {@code ConcurrentInitializer} throws + * an exception + */ + public static T initialize(final ConcurrentInitializer initializer) + throws ConcurrentException { + return initializer != null ? initializer.get() : null; + } + + /** + * Invokes the specified {@code ConcurrentInitializer} and transforms + * occurring exceptions to runtime exceptions. This method works like + * {@link #initialize(ConcurrentInitializer)}, but if the {@code + * ConcurrentInitializer} throws a {@link ConcurrentException}, it is + * caught, and the cause is wrapped in a {@link ConcurrentRuntimeException}. + * So client code does not have to deal with checked exceptions. + * + * @param the type of the object produced by the initializer + * @param initializer the {@code ConcurrentInitializer} to be invoked + * @return the object managed by the {@code ConcurrentInitializer} + * @throws ConcurrentRuntimeException if the initializer throws an exception + */ + public static T initializeUnchecked(final ConcurrentInitializer initializer) { + try { + return initialize(initializer); + } catch (final ConcurrentException cex) { + throw new ConcurrentRuntimeException(cex.getCause()); + } + } + + //----------------------------------------------------------------------- + /** + *

              + * Puts a value in the specified {@code ConcurrentMap} if the key is not yet + * present. This method works similar to the {@code putIfAbsent()} method of + * the {@code ConcurrentMap} interface, but the value returned is different. + * Basically, this method is equivalent to the following code fragment: + *

              + * + *
              +     * if (!map.containsKey(key)) {
              +     *     map.put(key, value);
              +     *     return value;
              +     * } else {
              +     *     return map.get(key);
              +     * }
              +     * 
              + * + *

              + * except that the action is performed atomically. So this method always + * returns the value which is stored in the map. + *

              + *

              + * This method is null-safe: It accepts a null map as input + * without throwing an exception. In this case the return value is + * null, too. + *

              + * + * @param the type of the keys of the map + * @param the type of the values of the map + * @param map the map to be modified + * @param key the key of the value to be added + * @param value the value to be added + * @return the value stored in the map after this operation + */ + public static V putIfAbsent(final ConcurrentMap map, final K key, final V value) { + if (map == null) { + return null; + } + + final V result = map.putIfAbsent(key, value); + return result != null ? result : value; + } + + /** + * Checks if a concurrent map contains a key and creates a corresponding + * value if not. This method first checks the presence of the key in the + * given map. If it is already contained, its value is returned. Otherwise + * the {@code get()} method of the passed in {@link ConcurrentInitializer} + * is called. With the resulting object + * {@link #putIfAbsent(ConcurrentMap, Object, Object)} is called. This + * handles the case that in the meantime another thread has added the key to + * the map. Both the map and the initializer can be null; in this + * case this method simply returns null. + * + * @param the type of the keys of the map + * @param the type of the values of the map + * @param map the map to be modified + * @param key the key of the value to be added + * @param init the {@link ConcurrentInitializer} for creating the value + * @return the value stored in the map after this operation; this may or may + * not be the object created by the {@link ConcurrentInitializer} + * @throws ConcurrentException if the initializer throws an exception + */ + public static V createIfAbsent(final ConcurrentMap map, final K key, + final ConcurrentInitializer init) throws ConcurrentException { + if (map == null || init == null) { + return null; + } + + final V value = map.get(key); + if (value == null) { + return putIfAbsent(map, key, init.get()); + } + return value; + } + + /** + * Checks if a concurrent map contains a key and creates a corresponding + * value if not, suppressing checked exceptions. This method calls + * {@code createIfAbsent()}. If a {@link ConcurrentException} is thrown, it + * is caught and re-thrown as a {@link ConcurrentRuntimeException}. + * + * @param the type of the keys of the map + * @param the type of the values of the map + * @param map the map to be modified + * @param key the key of the value to be added + * @param init the {@link ConcurrentInitializer} for creating the value + * @return the value stored in the map after this operation; this may or may + * not be the object created by the {@link ConcurrentInitializer} + * @throws ConcurrentRuntimeException if the initializer throws an exception + */ + public static V createIfAbsentUnchecked(final ConcurrentMap map, + final K key, final ConcurrentInitializer init) { + try { + return createIfAbsent(map, key, init); + } catch (final ConcurrentException cex) { + throw new ConcurrentRuntimeException(cex.getCause()); + } + } + + //----------------------------------------------------------------------- + /** + *

              + * Gets an implementation of Future that is immediately done + * and returns the specified constant value. + *

              + *

              + * This can be useful to return a simple constant immediately from the + * concurrent processing, perhaps as part of avoiding nulls. + * A constant future can also be useful in testing. + *

              + * + * @param the type of the value used by this {@code Future} object + * @param value the constant value to return, may be null + * @return an instance of Future that will return the value, never null + */ + public static Future constantFuture(final T value) { + return new ConstantFuture(value); + } + + /** + * A specialized {@code Future} implementation which wraps a constant value. + * @param the type of the value wrapped by this class + */ + static final class ConstantFuture implements Future { + /** The constant value. */ + private final T value; + + /** + * Creates a new instance of {@code ConstantFuture} and initializes it + * with the constant value. + * + * @param value the value (may be null) + */ + ConstantFuture(final T value) { + this.value = value; + } + + /** + * {@inheritDoc} This implementation always returns true because + * the constant object managed by this {@code Future} implementation is + * always available. + */ + @Override + public boolean isDone() { + return true; + } + + /** + * {@inheritDoc} This implementation just returns the constant value. + */ + @Override + public T get() { + return value; + } + + /** + * {@inheritDoc} This implementation just returns the constant value; it + * does not block, therefore the timeout has no meaning. + */ + @Override + public T get(final long timeout, final TimeUnit unit) { + return value; + } + + /** + * {@inheritDoc} This implementation always returns false; there + * is no background process which could be cancelled. + */ + @Override + public boolean isCancelled() { + return false; + } + + /** + * {@inheritDoc} The cancel operation is not supported. This + * implementation always returns false. + */ + @Override + public boolean cancel(final boolean mayInterruptIfRunning) { + return false; + } + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConstantInitializer.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConstantInitializer.java new file mode 100644 index 000000000..007b2de8d --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/ConstantInitializer.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +import com.fr.third.org.apache.commons.lang3.ObjectUtils; + +/** + *

              + * A very simple implementation of the {@link ConcurrentInitializer} interface + * which always returns the same object. + *

              + *

              + * An instance of this class is passed a reference to an object when it is + * constructed. The {@link #get()} method just returns this object. No + * synchronization is required. + *

              + *

              + * This class is useful for instance for unit testing or in cases where a + * specific object has to be passed to an object which expects a + * {@link ConcurrentInitializer}. + *

              + * + * @since 3.0 + * @version $Id: ConstantInitializer.java 1557584 2014-01-12 18:26:49Z britter $ + * @param the type of the object managed by this initializer + */ +public class ConstantInitializer implements ConcurrentInitializer { + /** Constant for the format of the string representation. */ + private static final String FMT_TO_STRING = "ConstantInitializer@%d [ object = %s ]"; + + /** Stores the managed object. */ + private final T object; + + /** + * Creates a new instance of {@code ConstantInitializer} and initializes it + * with the object to be managed. The {@code get()} method will always + * return the object passed here. This class does not place any restrictions + * on the object. It may be null, then {@code get()} will return + * null, too. + * + * @param obj the object to be managed by this initializer + */ + public ConstantInitializer(final T obj) { + object = obj; + } + + /** + * Directly returns the object that was passed to the constructor. This is + * the same object as returned by {@code get()}. However, this method does + * not declare that it throws an exception. + * + * @return the object managed by this initializer + */ + public final T getObject() { + return object; + } + + /** + * Returns the object managed by this initializer. This implementation just + * returns the object passed to the constructor. + * + * @return the object managed by this initializer + * @throws ConcurrentException if an error occurs + */ + @Override + public T get() throws ConcurrentException { + return getObject(); + } + + /** + * Returns a hash code for this object. This implementation returns the hash + * code of the managed object. + * + * @return a hash code for this object + */ + @Override + public int hashCode() { + return getObject() != null ? getObject().hashCode() : 0; + } + + /** + * Compares this object with another one. This implementation returns + * true if and only if the passed in object is an instance of + * {@code ConstantInitializer} which refers to an object equals to the + * object managed by this instance. + * + * @param obj the object to compare to + * @return a flag whether the objects are equal + */ + @SuppressWarnings( "deprecation" ) // ObjectUtils.equals(Object, Object) has been deprecated in 3.2 + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ConstantInitializer)) { + return false; + } + + final ConstantInitializer c = (ConstantInitializer) obj; + return ObjectUtils.equals(getObject(), c.getObject()); + } + + /** + * Returns a string representation for this object. This string also + * contains a string representation of the object managed by this + * initializer. + * + * @return a string for this object + */ + @Override + public String toString() { + return String.format(FMT_TO_STRING, Integer.valueOf(System.identityHashCode(this)), + String.valueOf(getObject())); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/LazyInitializer.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/LazyInitializer.java new file mode 100644 index 000000000..c954766b3 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/LazyInitializer.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +/** + *

              + * This class provides a generic implementation of the lazy initialization + * pattern. + *

              + *

              + * Sometimes an application has to deal with an object only under certain + * circumstances, e.g. when the user selects a specific menu item or if a + * special event is received. If the creation of the object is costly or the + * consumption of memory or other system resources is significant, it may make + * sense to defer the creation of this object until it is really needed. This is + * a use case for the lazy initialization pattern. + *

              + *

              + * This abstract base class provides an implementation of the double-check idiom + * for an instance field as discussed in Joshua Bloch's "Effective Java", 2nd + * edition, item 71. The class already implements all necessary synchronization. + * A concrete subclass has to implement the {@code initialize()} method, which + * actually creates the wrapped data object. + *

              + *

              + * As an usage example consider that we have a class {@code ComplexObject} whose + * instantiation is a complex operation. In order to apply lazy initialization + * to this class, a subclass of {@code LazyInitializer} has to be created: + *

              + * + *
              + * public class ComplexObjectInitializer extends LazyInitializer<ComplexObject> {
              + *     @Override
              + *     protected ComplexObject initialize() {
              + *         return new ComplexObject();
              + *     }
              + * }
              + * 
              + * + *

              + * Access to the data object is provided through the {@code get()} method. So, + * code that wants to obtain the {@code ComplexObject} instance would simply + * look like this: + *

              + * + *
              + * // Create an instance of the lazy initializer
              + * ComplexObjectInitializer initializer = new ComplexObjectInitializer();
              + * ...
              + * // When the object is actually needed:
              + * ComplexObject cobj = initializer.get();
              + * 
              + * + *

              + * If multiple threads call the {@code get()} method when the object has not yet + * been created, they are blocked until initialization completes. The algorithm + * guarantees that only a single instance of the wrapped object class is + * created, which is passed to all callers. Once initialized, calls to the + * {@code get()} method are pretty fast because no synchronization is needed + * (only an access to a volatile member field). + *

              + * + * @since 3.0 + * @version $Id: LazyInitializer.java 1583482 2014-03-31 22:54:57Z niallp $ + * @param the type of the object managed by this initializer class + */ +public abstract class LazyInitializer implements ConcurrentInitializer { + /** Stores the managed object. */ + private volatile T object; + + /** + * Returns the object wrapped by this instance. On first access the object + * is created. After that it is cached and can be accessed pretty fast. + * + * @return the object initialized by this {@code LazyInitializer} + * @throws ConcurrentException if an error occurred during initialization of + * the object + */ + @Override + public T get() throws ConcurrentException { + // use a temporary variable to reduce the number of reads of the + // volatile field + T result = object; + + if (result == null) { + synchronized (this) { + result = object; + if (result == null) { + object = result = initialize(); + } + } + } + + return result; + } + + /** + * Creates and initializes the object managed by this {@code + * LazyInitializer}. This method is called by {@link #get()} when the object + * is accessed for the first time. An implementation can focus on the + * creation of the object. No synchronization is needed, as this is already + * handled by {@code get()}. + * + * @return the managed data object + * @throws ConcurrentException if an error occurs during object creation + */ + protected abstract T initialize() throws ConcurrentException; +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java new file mode 100644 index 000000000..d0fe1398c --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java @@ -0,0 +1,352 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.ExecutorService; + +/** + *

              + * A specialized {@link BackgroundInitializer} implementation that can deal with + * multiple background initialization tasks. + *

              + *

              + * This class has a similar purpose as {@link BackgroundInitializer}. However, + * it is not limited to a single background initialization task. Rather it + * manages an arbitrary number of {@code BackgroundInitializer} objects, + * executes them, and waits until they are completely initialized. This is + * useful for applications that have to perform multiple initialization tasks + * that can run in parallel (i.e. that do not depend on each other). This class + * takes care about the management of an {@code ExecutorService} and shares it + * with the {@code BackgroundInitializer} objects it is responsible for; so the + * using application need not bother with these details. + *

              + *

              + * The typical usage scenario for {@code MultiBackgroundInitializer} is as + * follows: + *

              + *
                + *
              • Create a new instance of the class. Optionally pass in a pre-configured + * {@code ExecutorService}. Alternatively {@code MultiBackgroundInitializer} can + * create a temporary {@code ExecutorService} and delete it after initialization + * is complete.
              • + *
              • Create specialized {@link BackgroundInitializer} objects for the + * initialization tasks to be performed and add them to the {@code + * MultiBackgroundInitializer} using the + * {@link #addInitializer(String, BackgroundInitializer)} method.
              • + *
              • After all initializers have been added, call the {@link #start()} method. + *
              • + *
              • When access to the result objects produced by the {@code + * BackgroundInitializer} objects is needed call the {@link #get()} method. The + * object returned here provides access to all result objects created during + * initialization. It also stores information about exceptions that have + * occurred.
              • + *
              + *

              + * {@code MultiBackgroundInitializer} starts a special controller task that + * starts all {@code BackgroundInitializer} objects added to the instance. + * Before the an initializer is started it is checked whether this initializer + * already has an {@code ExecutorService} set. If this is the case, this {@code + * ExecutorService} is used for running the background task. Otherwise the + * current {@code ExecutorService} of this {@code MultiBackgroundInitializer} is + * shared with the initializer. + *

              + *

              + * The easiest way of using this class is to let it deal with the management of + * an {@code ExecutorService} itself: If no external {@code ExecutorService} is + * provided, the class creates a temporary {@code ExecutorService} (that is + * capable of executing all background tasks in parallel) and destroys it at the + * end of background processing. + *

              + *

              + * Alternatively an external {@code ExecutorService} can be provided - either at + * construction time or later by calling the + * {@link #setExternalExecutor(ExecutorService)} method. In this case all + * background tasks are scheduled at this external {@code ExecutorService}. + * Important note: When using an external {@code + * ExecutorService} be sure that the number of threads managed by the service is + * large enough. Otherwise a deadlock can happen! This is the case in the + * following scenario: {@code MultiBackgroundInitializer} starts a task that + * starts all registered {@code BackgroundInitializer} objects and waits for + * their completion. If for instance a single threaded {@code ExecutorService} + * is used, none of the background tasks can be executed, and the task created + * by {@code MultiBackgroundInitializer} waits forever. + *

              + * + * @since 3.0 + * @version $Id: MultiBackgroundInitializer.java 1583482 2014-03-31 22:54:57Z niallp $ + */ +public class MultiBackgroundInitializer + extends + BackgroundInitializer { + /** A map with the child initializers. */ + private final Map> childInitializers = + new HashMap>(); + + /** + * Creates a new instance of {@code MultiBackgroundInitializer}. + */ + public MultiBackgroundInitializer() { + super(); + } + + /** + * Creates a new instance of {@code MultiBackgroundInitializer} and + * initializes it with the given external {@code ExecutorService}. + * + * @param exec the {@code ExecutorService} for executing the background + * tasks + */ + public MultiBackgroundInitializer(final ExecutorService exec) { + super(exec); + } + + /** + * Adds a new {@code BackgroundInitializer} to this object. When this + * {@code MultiBackgroundInitializer} is started, the given initializer will + * be processed. This method must not be called after {@link #start()} has + * been invoked. + * + * @param name the name of the initializer (must not be null) + * @param init the {@code BackgroundInitializer} to add (must not be + * null) + * @throws IllegalArgumentException if a required parameter is missing + * @throws IllegalStateException if {@code start()} has already been called + */ + public void addInitializer(final String name, final BackgroundInitializer init) { + if (name == null) { + throw new IllegalArgumentException( + "Name of child initializer must not be null!"); + } + if (init == null) { + throw new IllegalArgumentException( + "Child initializer must not be null!"); + } + + synchronized (this) { + if (isStarted()) { + throw new IllegalStateException( + "addInitializer() must not be called after start()!"); + } + childInitializers.put(name, init); + } + } + + /** + * Returns the number of tasks needed for executing all child {@code + * BackgroundInitializer} objects in parallel. This implementation sums up + * the required tasks for all child initializers (which is necessary if one + * of the child initializers is itself a {@code MultiBackgroundInitializer} + * ). Then it adds 1 for the control task that waits for the completion of + * the children. + * + * @return the number of tasks required for background processing + */ + @Override + protected int getTaskCount() { + int result = 1; + + for (final BackgroundInitializer bi : childInitializers.values()) { + result += bi.getTaskCount(); + } + + return result; + } + + /** + * Creates the results object. This implementation starts all child {@code + * BackgroundInitializer} objects. Then it collects their results and + * creates a {@code MultiBackgroundInitializerResults} object with this + * data. If a child initializer throws a checked exceptions, it is added to + * the results object. Unchecked exceptions are propagated. + * + * @return the results object + * @throws Exception if an error occurs + */ + @Override + protected MultiBackgroundInitializerResults initialize() throws Exception { + Map> inits; + synchronized (this) { + // create a snapshot to operate on + inits = new HashMap>( + childInitializers); + } + + // start the child initializers + final ExecutorService exec = getActiveExecutor(); + for (final BackgroundInitializer bi : inits.values()) { + if (bi.getExternalExecutor() == null) { + // share the executor service if necessary + bi.setExternalExecutor(exec); + } + bi.start(); + } + + // collect the results + final Map results = new HashMap(); + final Map excepts = new HashMap(); + for (final Map.Entry> e : inits.entrySet()) { + try { + results.put(e.getKey(), e.getValue().get()); + } catch (final ConcurrentException cex) { + excepts.put(e.getKey(), cex); + } + } + + return new MultiBackgroundInitializerResults(inits, results, excepts); + } + + /** + * A data class for storing the results of the background initialization + * performed by {@code MultiBackgroundInitializer}. Objects of this inner + * class are returned by {@link MultiBackgroundInitializer#initialize()}. + * They allow access to all result objects produced by the + * {@link BackgroundInitializer} objects managed by the owning instance. It + * is also possible to retrieve status information about single + * {@link BackgroundInitializer}s, i.e. whether they completed normally or + * caused an exception. + */ + public static class MultiBackgroundInitializerResults { + /** A map with the child initializers. */ + private final Map> initializers; + + /** A map with the result objects. */ + private final Map resultObjects; + + /** A map with the exceptions. */ + private final Map exceptions; + + /** + * Creates a new instance of {@code MultiBackgroundInitializerResults} + * and initializes it with maps for the {@code BackgroundInitializer} + * objects, their result objects and the exceptions thrown by them. + * + * @param inits the {@code BackgroundInitializer} objects + * @param results the result objects + * @param excepts the exceptions + */ + private MultiBackgroundInitializerResults( + final Map> inits, + final Map results, + final Map excepts) { + initializers = inits; + resultObjects = results; + exceptions = excepts; + } + + /** + * Returns the {@code BackgroundInitializer} with the given name. If the + * name cannot be resolved, an exception is thrown. + * + * @param name the name of the {@code BackgroundInitializer} + * @return the {@code BackgroundInitializer} with this name + * @throws NoSuchElementException if the name cannot be resolved + */ + public BackgroundInitializer getInitializer(final String name) { + return checkName(name); + } + + /** + * Returns the result object produced by the {@code + * BackgroundInitializer} with the given name. This is the object + * returned by the initializer's {@code initialize()} method. If this + * {@code BackgroundInitializer} caused an exception, null is + * returned. If the name cannot be resolved, an exception is thrown. + * + * @param name the name of the {@code BackgroundInitializer} + * @return the result object produced by this {@code + * BackgroundInitializer} + * @throws NoSuchElementException if the name cannot be resolved + */ + public Object getResultObject(final String name) { + checkName(name); + return resultObjects.get(name); + } + + /** + * Returns a flag whether the {@code BackgroundInitializer} with the + * given name caused an exception. + * + * @param name the name of the {@code BackgroundInitializer} + * @return a flag whether this initializer caused an exception + * @throws NoSuchElementException if the name cannot be resolved + */ + public boolean isException(final String name) { + checkName(name); + return exceptions.containsKey(name); + } + + /** + * Returns the {@code ConcurrentException} object that was thrown by the + * {@code BackgroundInitializer} with the given name. If this + * initializer did not throw an exception, the return value is + * null. If the name cannot be resolved, an exception is thrown. + * + * @param name the name of the {@code BackgroundInitializer} + * @return the exception thrown by this initializer + * @throws NoSuchElementException if the name cannot be resolved + */ + public ConcurrentException getException(final String name) { + checkName(name); + return exceptions.get(name); + } + + /** + * Returns a set with the names of all {@code BackgroundInitializer} + * objects managed by the {@code MultiBackgroundInitializer}. + * + * @return an (unmodifiable) set with the names of the managed {@code + * BackgroundInitializer} objects + */ + public Set initializerNames() { + return Collections.unmodifiableSet(initializers.keySet()); + } + + /** + * Returns a flag whether the whole initialization was successful. This + * is the case if no child initializer has thrown an exception. + * + * @return a flag whether the initialization was successful + */ + public boolean isSuccessful() { + return exceptions.isEmpty(); + } + + /** + * Checks whether an initializer with the given name exists. If not, + * throws an exception. If it exists, the associated child initializer + * is returned. + * + * @param name the name to check + * @return the initializer with this name + * @throws NoSuchElementException if the name is unknown + */ + private BackgroundInitializer checkName(final String name) { + final BackgroundInitializer init = initializers.get(name); + if (init == null) { + throw new NoSuchElementException( + "No child initializer with name " + name); + } + + return init; + } + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/TimedSemaphore.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/TimedSemaphore.java new file mode 100644 index 000000000..77c968d40 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/TimedSemaphore.java @@ -0,0 +1,424 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.concurrent; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import com.fr.third.org.apache.commons.lang3.Validate; + +/** + *

              + * A specialized semaphore implementation that provides a number of + * permits in a given time frame. + *

              + *

              + * This class is similar to the {@code java.util.concurrent.Semaphore} class + * provided by the JDK in that it manages a configurable number of permits. + * Using the {@link #acquire()} method a permit can be requested by a thread. + * However, there is an additional timing dimension: there is no {@code + * release()} method for freeing a permit, but all permits are automatically + * released at the end of a configurable time frame. If a thread calls + * {@link #acquire()} and the available permits are already exhausted for this + * time frame, the thread is blocked. When the time frame ends all permits + * requested so far are restored, and blocking threads are waked up again, so + * that they can try to acquire a new permit. This basically means that in the + * specified time frame only the given number of operations is possible. + *

              + *

              + * A use case for this class is to artificially limit the load produced by a + * process. As an example consider an application that issues database queries + * on a production system in a background process to gather statistical + * information. This background processing should not produce so much database + * load that the functionality and the performance of the production system are + * impacted. Here a {@code TimedSemaphore} could be installed to guarantee that + * only a given number of database queries are issued per second. + *

              + *

              + * A thread class for performing database queries could look as follows: + *

              + * + *
              + * public class StatisticsThread extends Thread {
              + *     // The semaphore for limiting database load.
              + *     private final TimedSemaphore semaphore;
              + *     // Create an instance and set the semaphore
              + *     public StatisticsThread(TimedSemaphore timedSemaphore) {
              + *         semaphore = timedSemaphore;
              + *     }
              + *     // Gather statistics
              + *     public void run() {
              + *         try {
              + *             while(true) {
              + *                 semaphore.acquire();   // limit database load
              + *                 performQuery();        // issue a query
              + *             }
              + *         } catch(InterruptedException) {
              + *             // fall through
              + *         }
              + *     }
              + *     ...
              + * }
              + * 
              + * + *

              + * The following code fragment shows how a {@code TimedSemaphore} is created + * that allows only 10 operations per second and passed to the statistics + * thread: + *

              + * + *
              + * TimedSemaphore sem = new TimedSemaphore(1, TimeUnit.SECOND, 10);
              + * StatisticsThread thread = new StatisticsThread(sem);
              + * thread.start();
              + * 
              + * + *

              + * When creating an instance the time period for the semaphore must be + * specified. {@code TimedSemaphore} uses an executor service with a + * corresponding period to monitor this interval. The {@code + * ScheduledExecutorService} to be used for this purpose can be provided at + * construction time. Alternatively the class creates an internal executor + * service. + *

              + *

              + * Client code that uses {@code TimedSemaphore} has to call the + * {@link #acquire()} method in aach processing step. {@code TimedSemaphore} + * keeps track of the number of invocations of the {@link #acquire()} method and + * blocks the calling thread if the counter exceeds the limit specified. When + * the timer signals the end of the time period the counter is reset and all + * waiting threads are released. Then another cycle can start. + *

              + *

              + * It is possible to modify the limit at any time using the + * {@link #setLimit(int)} method. This is useful if the load produced by an + * operation has to be adapted dynamically. In the example scenario with the + * thread collecting statistics it may make sense to specify a low limit during + * day time while allowing a higher load in the night time. Reducing the limit + * takes effect immediately by blocking incoming callers. If the limit is + * increased, waiting threads are not released immediately, but wake up when the + * timer runs out. Then, in the next period more processing steps can be + * performed without blocking. By setting the limit to 0 the semaphore can be + * switched off: in this mode the {@link #acquire()} method never blocks, but + * lets all callers pass directly. + *

              + *

              + * When the {@code TimedSemaphore} is no more needed its {@link #shutdown()} + * method should be called. This causes the periodic task that monitors the time + * interval to be canceled. If the {@code ScheduledExecutorService} has been + * created by the semaphore at construction time, it is also shut down. + * resources. After that {@link #acquire()} must not be called any more. + *

              + * + * @since 3.0 + * @version $Id: TimedSemaphore.java 1593668 2014-05-10 05:58:17Z djones $ + */ +public class TimedSemaphore { + /** + * Constant for a value representing no limit. If the limit is set to a + * value less or equal this constant, the {@code TimedSemaphore} will be + * effectively switched off. + */ + public static final int NO_LIMIT = 0; + + /** Constant for the thread pool size for the executor. */ + private static final int THREAD_POOL_SIZE = 1; + + /** The executor service for managing the timer thread. */ + private final ScheduledExecutorService executorService; + + /** Stores the period for this timed semaphore. */ + private final long period; + + /** The time unit for the period. */ + private final TimeUnit unit; + + /** A flag whether the executor service was created by this object. */ + private final boolean ownExecutor; + + /** A future object representing the timer task. */ + private ScheduledFuture task; // @GuardedBy("this") + + /** Stores the total number of invocations of the acquire() method. */ + private long totalAcquireCount; // @GuardedBy("this") + + /** + * The counter for the periods. This counter is increased every time a + * period ends. + */ + private long periodCount; // @GuardedBy("this") + + /** The limit. */ + private int limit; // @GuardedBy("this") + + /** The current counter. */ + private int acquireCount; // @GuardedBy("this") + + /** The number of invocations of acquire() in the last period. */ + private int lastCallsPerPeriod; // @GuardedBy("this") + + /** A flag whether shutdown() was called. */ + private boolean shutdown; // @GuardedBy("this") + + /** + * Creates a new instance of {@link TimedSemaphore} and initializes it with + * the given time period and the limit. + * + * @param timePeriod the time period + * @param timeUnit the unit for the period + * @param limit the limit for the semaphore + * @throws IllegalArgumentException if the period is less or equals 0 + */ + public TimedSemaphore(final long timePeriod, final TimeUnit timeUnit, final int limit) { + this(null, timePeriod, timeUnit, limit); + } + + /** + * Creates a new instance of {@link TimedSemaphore} and initializes it with + * an executor service, the given time period, and the limit. The executor + * service will be used for creating a periodic task for monitoring the time + * period. It can be null, then a default service will be created. + * + * @param service the executor service + * @param timePeriod the time period + * @param timeUnit the unit for the period + * @param limit the limit for the semaphore + * @throws IllegalArgumentException if the period is less or equals 0 + */ + public TimedSemaphore(final ScheduledExecutorService service, final long timePeriod, + final TimeUnit timeUnit, final int limit) { + Validate.inclusiveBetween(1, Long.MAX_VALUE, timePeriod, "Time period must be greater than 0!"); + + period = timePeriod; + unit = timeUnit; + + if (service != null) { + executorService = service; + ownExecutor = false; + } else { + final ScheduledThreadPoolExecutor s = new ScheduledThreadPoolExecutor( + THREAD_POOL_SIZE); + s.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); + s.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + executorService = s; + ownExecutor = true; + } + + setLimit(limit); + } + + /** + * Returns the limit enforced by this semaphore. The limit determines how + * many invocations of {@link #acquire()} are allowed within the monitored + * period. + * + * @return the limit + */ + public final synchronized int getLimit() { + return limit; + } + + /** + * Sets the limit. This is the number of times the {@link #acquire()} method + * can be called within the time period specified. If this limit is reached, + * further invocations of {@link #acquire()} will block. Setting the limit + * to a value <= {@link #NO_LIMIT} will cause the limit to be disabled, + * i.e. an arbitrary number of{@link #acquire()} invocations is allowed in + * the time period. + * + * @param limit the limit + */ + public final synchronized void setLimit(final int limit) { + this.limit = limit; + } + + /** + * Initializes a shutdown. After that the object cannot be used any more. + * This method can be invoked an arbitrary number of times. All invocations + * after the first one do not have any effect. + */ + public synchronized void shutdown() { + if (!shutdown) { + + if (ownExecutor) { + // if the executor was created by this instance, it has + // to be shutdown + getExecutorService().shutdownNow(); + } + if (task != null) { + task.cancel(false); + } + + shutdown = true; + } + } + + /** + * Tests whether the {@link #shutdown()} method has been called on this + * object. If this method returns true, this instance cannot be used + * any longer. + * + * @return a flag whether a shutdown has been performed + */ + public synchronized boolean isShutdown() { + return shutdown; + } + + /** + * Tries to acquire a permit from this semaphore. This method will block if + * the limit for the current period has already been reached. If + * {@link #shutdown()} has already been invoked, calling this method will + * cause an exception. The very first call of this method starts the timer + * task which monitors the time period set for this {@code TimedSemaphore}. + * From now on the semaphore is active. + * + * @throws InterruptedException if the thread gets interrupted + * @throws IllegalStateException if this semaphore is already shut down + */ + public synchronized void acquire() throws InterruptedException { + if (isShutdown()) { + throw new IllegalStateException("TimedSemaphore is shut down!"); + } + + if (task == null) { + task = startTimer(); + } + + boolean canPass = false; + do { + canPass = getLimit() <= NO_LIMIT || acquireCount < getLimit(); + if (!canPass) { + wait(); + } else { + acquireCount++; + } + } while (!canPass); + } + + /** + * Returns the number of (successful) acquire invocations during the last + * period. This is the number of times the {@link #acquire()} method was + * called without blocking. This can be useful for testing or debugging + * purposes or to determine a meaningful threshold value. If a limit is set, + * the value returned by this method won't be greater than this limit. + * + * @return the number of non-blocking invocations of the {@link #acquire()} + * method + */ + public synchronized int getLastAcquiresPerPeriod() { + return lastCallsPerPeriod; + } + + /** + * Returns the number of invocations of the {@link #acquire()} method for + * the current period. This may be useful for testing or debugging purposes. + * + * @return the current number of {@link #acquire()} invocations + */ + public synchronized int getAcquireCount() { + return acquireCount; + } + + /** + * Returns the number of calls to the {@link #acquire()} method that can + * still be performed in the current period without blocking. This method + * can give an indication whether it is safe to call the {@link #acquire()} + * method without risking to be suspended. However, there is no guarantee + * that a subsequent call to {@link #acquire()} actually is not-blocking + * because in the mean time other threads may have invoked the semaphore. + * + * @return the current number of available {@link #acquire()} calls in the + * current period + */ + public synchronized int getAvailablePermits() { + return getLimit() - getAcquireCount(); + } + + /** + * Returns the average number of successful (i.e. non-blocking) + * {@link #acquire()} invocations for the entire life-time of this {@code + * TimedSemaphore}. This method can be used for instance for statistical + * calculations. + * + * @return the average number of {@link #acquire()} invocations per time + * unit + */ + public synchronized double getAverageCallsPerPeriod() { + return periodCount == 0 ? 0 : (double) totalAcquireCount + / (double) periodCount; + } + + /** + * Returns the time period. This is the time monitored by this semaphore. + * Only a given number of invocations of the {@link #acquire()} method is + * possible in this period. + * + * @return the time period + */ + public long getPeriod() { + return period; + } + + /** + * Returns the time unit. This is the unit used by {@link #getPeriod()}. + * + * @return the time unit + */ + public TimeUnit getUnit() { + return unit; + } + + /** + * Returns the executor service used by this instance. + * + * @return the executor service + */ + protected ScheduledExecutorService getExecutorService() { + return executorService; + } + + /** + * Starts the timer. This method is called when {@link #acquire()} is called + * for the first time. It schedules a task to be executed at fixed rate to + * monitor the time period specified. + * + * @return a future object representing the task scheduled + */ + protected ScheduledFuture startTimer() { + return getExecutorService().scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + endOfPeriod(); + } + }, getPeriod(), getPeriod(), getUnit()); + } + + /** + * The current time period is finished. This method is called by the timer + * used internally to monitor the time period. It resets the counter and + * releases the threads waiting for this barrier. + */ + synchronized void endOfPeriod() { + lastCallsPerPeriod = acquireCount; + totalAcquireCount += acquireCount; + periodCount++; + acquireCount = 0; + notifyAll(); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/package-info.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/package-info.java new file mode 100644 index 000000000..d01809f5c --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/concurrent/package-info.java @@ -0,0 +1,440 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *

              Provides support classes for multi-threaded programming. + * This package is intended to be an extension to {@link java.util.concurrent}. + * These classes are thread-safe.

              + * + *

              + * + *

              A group of classes deals with the correct creation and initialization of objects that are accessed by multiple threads. + * All these classes implement the {@link com.fr.third.org.apache.commons.lang3.concurrent.ConcurrentInitializer} interface which provides just a + * single method: + *

              + * + *
              + * 
              + * public interface ConcurrentInitializer<T> {
              + *    T get() throws ConcurrentException;
              + * }
              + * 
              + * 
              + * + *

              A ConcurrentInitializer produces an object. + * By calling the {@link com.fr.third.org.apache.commons.lang3.concurrent.ConcurrentInitializer#get() get()} method the object managed by the initializer can be obtained. + * There are different implementations of the interface available + * addressing various use cases: + *

              + * + *

              {@link com.fr.third.org.apache.commons.lang3.concurrent.ConstantInitializer} is a very straightforward implementation of the ConcurrentInitializer interface: + * An instance is passed an object when it is constructed. + * In its get() method it simply returns this object. + * This is useful, for instance in unit tests or in cases when you want to pass a specific object to a component which expects a ConcurrentInitializer. + *

              + * + *

              The {@link com.fr.third.org.apache.commons.lang3.concurrent.LazyInitializer} class can be used to defer the creation of an object until it is actually used. + * This makes sense, for instance, if the creation of the object is expensive and would slow down application startup or if the object is needed only for special executions. + * LazyInitializer implements the double-check idiom for an instance field as discussed in Joshua Bloch's "Effective Java", 2nd edition, item 71. + * It uses volatile fields to reduce the amount of synchronization. + * Note that this idiom is appropriate for instance fields only. + * For static fields there are superior alternatives.

              + * + *

              We provide an example use case to demonstrate the usage of this class: + * A server application uses multiple worker threads to process client requests. + * If such a request causes a fatal error, an administrator is to be notified using a special messaging service. + * We assume that the creation of the messaging service is an expensive operation. + * So it should only be performed if an error actually occurs. + * Here is where LazyInitializer comes into play. + * We create a specialized subclass for creating and initializing an instance of our messaging service. + * LazyInitializer declares an abstract {@link com.fr.third.org.apache.commons.lang3.concurrent.LazyInitializer#initialize() initialize()} method which we have to implement to create the messaging service object:

              + * + *
              + * 
              + * public class MessagingServiceInitializer extends LazyInitializer<MessagingService> {
              + *   protected MessagingService initialize() throws ConcurrentException {
              + *     // Do all necessary steps to create and initialize the service object
              + *     MessagingService service = ...
              + *     return service;
              + *   }
              + * }
              + * 
              + * 
              + * + *

              Now each server thread is passed a reference to a shared instance of our new MessagingServiceInitializer class. + * The threads run in a loop processing client requests. If an error is detected, the messaging service is obtained from the initializer, and the administrator is notified:

              + * + *
              + * 
              + * public class ServerThread implements Runnable {
              + *  // The initializer for obtaining the messaging service.
              + *  private final ConcurrentInitializer<MessagingService> initializer;
              + *
              + *  public ServerThread(ConcurrentInitializer<MessagingService> init) {
              + *    initializer = init;
              + *  }
              + *
              + *  public void run() {
              + *    while (true) {
              + *      try {
              + *        // wait for request
              + *        // process request
              + *      } catch (FatalServerException ex) {
              + *        // get messaging service
              + *        try {
              + *          MessagingService svc = initializer.get();
              + *          svc.notifyAdministrator(ex);
              + *        } catch (ConcurrentException cex) {
              + *          cex.printStackTrace();
              + *        }
              + *      }
              + *    }
              + *  }
              + * }
              + * 
              + * 
              + * + *

              The {@link com.fr.third.org.apache.commons.lang3.concurrent.AtomicInitializer} class is very similar to LazyInitializer. + * It serves the same purpose: to defer the creation of an object until it is needed. + * The internal structure is also very similar. + * Again there is an abstract {@link com.fr.third.org.apache.commons.lang3.concurrent.AtomicInitializer#initialize() initialize()} method which has to be implemented by concrete subclasses in order to create and initialize the managed object. + * Actually, in our example above we can turn the MessagingServiceInitializer into an atomic initializer by simply changing the extends declaration to refer to AtomicInitializer<MessagingService> as super class.

              + * + *

              With {@link com.fr.third.org.apache.commons.lang3.concurrent.AtomicSafeInitializer} there is yet another variant implementing the lazy initializing pattern. + * Its implementation is close to AtomicInitializer; it also uses atomic variables internally and therefore does not need synchronization. + * The name "Safe" is derived from the fact that it implements an additional check which guarantees that the {@link com.fr.third.org.apache.commons.lang3.concurrent.AtomicSafeInitializer#initialize() initialize()} method is called only once. + * So it behaves exactly in the same way as LazyInitializer.

              + * + *

              Now, which one of the lazy initializer implementations should you use? + * First of all we have to state that is is problematic to give general recommendations regarding the performance of these classes. + * The initializers make use of low-level functionality whose efficiency depends on multiple factors including the target platform and the number of concurrent threads. + * So developers should make their own benchmarks in scenarios close to their specific use cases. + * The following statements are rules of thumb which have to be verified in practice.

              + * + *

              AtomicInitializer is probably the most efficient implementation due to its lack of synchronization and further checks. + * Its main drawback is that the initialize() method can be called multiple times. + * In cases where this is not an issue AtomicInitializer is a good choice. + * AtomicSafeInitializer and LazyInitializer both guarantee that the initialization method is called only once. + * Because AtomicSafeInitializer does not use synchronization it is probably slightly more efficient than LazyInitializer, but the concrete numbers might depend on the level of concurrency.

              + * + *

              Another implementation of the ConcurrentInitializer interface is {@link com.fr.third.org.apache.commons.lang3.concurrent.BackgroundInitializer}. + * It is again an abstract base class with an {@link com.fr.third.org.apache.commons.lang3.concurrent.BackgroundInitializer#initialize() initialize()} method that has to be defined by concrete subclasses. + * The idea of BackgroundInitializer is that it calls the initialize() method in a separate worker thread. + * An application creates a background initializer and starts it. + * Then it can continue with its work while the initializer runs in parallel. + * When the application needs the results of the initializer it calls its get() method. + * get() blocks until the initialization is complete. + * This is useful for instance at application startup. + * Here initialization steps (e.g. reading configuration files, opening a database connection, etc.) can be run in background threads while the application shows a splash screen and constructs its UI.

              + * + *

              As a concrete example consider an application that has to read the content of a URL - maybe a page with news - which is to be displayed to the user after login. + * Because loading the data over the network can take some time a specialized implementation of BackgroundInitializer can be created for this purpose:

              + * + *
              + * 
              + * public class URLLoader extends BackgroundInitializer<String> {
              + *   // The URL to be loaded.
              + *   private final URL url;
              + *
              + *   public URLLoader(URL u) {
              + *     url = u;
              + *   }
              + *
              + *   protected String initialize() throws ConcurrentException {
              + *     try {
              + *       InputStream in = url.openStream();
              + *       // read content into string
              + *       ...
              + *       return content;
              + *     } catch (IOException ioex) {
              + *       throw new ConcurrentException(ioex);
              + *     }
              + *   }
              + * }
              + * 
              + * 
              + * + *

              An application creates an instance of URLLoader and starts it. + * Then it can do other things. + * When it needs the content of the URL it calls the initializer's get() method:

              + * + *
              + * 
              + * URL url = new URL("http://www.application-home-page.com/");
              + * URLLoader loader = new URLLoader(url);
              + * loader.start();  // this starts the background initialization
              + *
              + * // do other stuff
              + * ...
              + * // now obtain the content of the URL
              + * String content;
              + * try {
              + *   content = loader.get();  // this may block
              + * } catch (ConcurrentException cex) {
              + *   content = "Error when loading URL " + url;
              + * }
              + * // display content
              + * 
              + * 
              + * + *

              Related to BackgroundInitializer is the {@link com.fr.third.org.apache.commons.lang3.concurrent.MultiBackgroundInitializer} class. + * As the name implies, this class can handle multiplie initializations in parallel. + * The basic usage scenario is that a MultiBackgroundInitializer instance is created. + * Then an arbitrary number of BackgroundInitializer objects is added using the {@link com.fr.third.org.apache.commons.lang3.concurrent.MultiBackgroundInitializer#addInitializer(String, BackgroundInitializer)} method. + * When adding an initializer a string has to be provided which is later used to obtain the result for this initializer. + * When all initializers have been added the {@link com.fr.third.org.apache.commons.lang3.concurrent.MultiBackgroundInitializer#start()} method is called. + * This starts processing of all initializers. + * Later the get() method can be called. + * It waits until all initializers have finished their initialization. + * get() returns an object of type {@link com.fr.third.org.apache.commons.lang3.concurrent.MultiBackgroundInitializer.MultiBackgroundInitializerResults}. + * This object provides information about all initializations that have been performed. + * It can be checked whether a specific initializer was successful or threw an exception. + * Of course, all initialization results can be queried.

              + * + *

              With MultiBackgroundInitializer we can extend our example to perform multiple initialization steps. + * Suppose that in addition to loading a web site we also want to create a JPA entity manager factory and read a configuration file. + * We assume that corresponding BackgroundInitializer implementations exist. + * The following example fragment shows the usage of MultiBackgroundInitializer for this purpose:

              + * + *
              + * 
              + * MultiBackgroundInitializer initializer = new MultiBackgroundInitializer();
              + * initializer.addInitializer("url", new URLLoader(url));
              + * initializer.addInitializer("jpa", new JPAEMFInitializer());
              + * initializer.addInitializer("config", new ConfigurationInitializer());
              + * initializer.start();  // start background processing
              + *
              + * // do other interesting things in parallel
              + * ...
              + * // evaluate the results of background initialization
              + * MultiBackgroundInitializer.MultiBackgroundInitializerResults results =
              + * initializer.get();
              + * String urlContent = (String) results.getResultObject("url");
              + * EntityManagerFactory emf =
              + * (EntityManagerFactory) results.getResultObject("jpa");
              + * ...
              + * 
              + * 
              + * + *

              The child initializers are added to the multi initializer and are assigned a unique name. + * The object returned by the get() method is then queried for the single results using these unique names.

              + * + *

              If background initializers - including MultiBackgroundInitializer - are created using the standard constructor, they create their own {@link java.util.concurrent.ExecutorService} which is used behind the scenes to execute the worker tasks. + * It is also possible to pass in an ExecutorService when the initializer is constructed. + * That way client code can configure the ExecutorService according to its specific needs; for instance, the number of threads available could be limited.

              + * + *

              Utility Classes

              + * + *

              Another group of classes in the new concurrent package offers some generic functionality related to concurrency. + * There is the {@link com.fr.third.org.apache.commons.lang3.concurrent.ConcurrentUtils} class with a bunch of static utility methods. + * One focus of this class is dealing with exceptions thrown by JDK classes. + * Many JDK classes of the executor framework throw exceptions of type {@link java.util.concurrent.ExecutionException} if something goes wrong. + * The root cause of these exceptions can also be a runtime exception or even an error. + * In typical Java programming you often do not want to deal with runtime exceptions directly; rather you let them fall through the hierarchy of method invocations until they reach a central exception handler. + * Checked exceptions in contrast are usually handled close to their occurrence. + * With ExecutionException this principle is violated. + * Because it is a checked exception, an application is forced to handle it even if the cause is a runtime exception. + * So you typically have to inspect the cause of the ExecutionException and test whether it is a checked exception which has to be handled. If this is not the case, the causing exception can be rethrown. + *

              + * + *

              The {@link com.fr.third.org.apache.commons.lang3.concurrent.ConcurrentUtils#extractCause(java.util.concurrent.ExecutionException)} method does this work for you. + * It is passed an ExecutionException and tests its root cause. + * If this is an error or a runtime exception, it is directly rethrown. + * Otherwise, an instance of {@link com.fr.third.org.apache.commons.lang3.concurrent.ConcurrentException} is created and initialized with the root cause + * (ConcurrentException is a new exception class in the o.a.c.l.concurrent package). + * So if you get such a ConcurrentException, you can be sure that the original cause for the ExecutionException was a checked exception. + * For users who prefer runtime exceptions in general there is also an {@link com.fr.third.org.apache.commons.lang3.concurrent.ConcurrentUtils#extractCauseUnchecked(java.util.concurrent.ExecutionException)} method which behaves like extractCause(), but returns the unchecked exception {@link com.fr.third.org.apache.commons.lang3.concurrent.ConcurrentRuntimeException} instead.

              + * + *

              In addition to the extractCause() methods there are corresponding {@link com.fr.third.org.apache.commons.lang3.concurrent.ConcurrentUtils#handleCause(java.util.concurrent.ExecutionException)} and {@link com.fr.third.org.apache.commons.lang3.concurrent.ConcurrentUtils#handleCauseUnchecked(java.util.concurrent.ExecutionException)} methods. + * These methods extract the cause of the passed in ExecutionException and throw the resulting ConcurrentException or ConcurrentRuntimeException. + * This makes it easy to transform an ExecutionException into a ConcurrentException ignoring unchecked exceptions:

              + * + *
              + * 
              + * Future<Object> future = ...;
              + * try {
              + *   Object result = future.get();
              + *   ...
              + * } catch (ExecutionException eex) {
              + *   ConcurrentUtils.handleCause(eex);
              + * }
              + * 
              + * 
              + * + *

              There is also some support for the concurrent initializers introduced in the last sub section. + * The initialize() method is passed a ConcurrentInitializer object and returns the object created by this initializer. + * It is null-safe. + * The initializeUnchecked() method works analogously, but a ConcurrentException throws by the initializer is rethrown as a ConcurrentRuntimeException. + * This is especially useful if the specific ConcurrentInitializer does not throw checked exceptions. + * Using this method the code for requesting the object of an initializer becomes less verbose. + * The direct invocation looks as follows:

              + * + *
              + * 
              + * ConcurrentInitializer<MyClass> initializer = ...;
              + * try {
              + *   MyClass obj = initializer.get();
              + *   // do something with obj
              + * } catch (ConcurrentException cex) {
              + *   // exception handling
              + * }
              + * 
              + * 
              + * + *

              Using the {@link com.fr.third.org.apache.commons.lang3.concurrent.ConcurrentUtils#initializeUnchecked(ConcurrentInitializer)} method, this becomes:

              + * + *
              + * 
              + * ConcurrentInitializer<MyClass> initializer = ...;
              + * MyClass obj = ConcurrentUtils.initializeUnchecked(initializer);
              + * // do something with obj
              + * 
              + * 
              + * + *

              Another utility class deals with the creation of threads. + * When using the Executor framework new in JDK 1.5 the developer usually does not have to care about creating threads; the executors create the threads they need on demand. + * However, sometimes it is desired to set some properties of the newly created worker threads. + * This is possible through the {@link java.util.concurrent.ThreadFactory} interface; an implementation of this interface has to be created and passed to an executor on creation time. + * Currently, the JDK does not provide an implementation of ThreadFactory, so one has to start from scratch.

              + * + *

              With {@link com.fr.third.org.apache.commons.lang3.concurrent.BasicThreadFactory} Commons Lang has an implementation of ThreadFactory that works out of the box for many common use cases. + * For instance, it is possible to set a naming pattern for the new threads, set the daemon flag and a priority, or install a handler for uncaught exceptions. + * Instances of BasicThreadFactory are created and configured using the nested {@link com.fr.third.org.apache.commons.lang3.concurrent.BasicThreadFactory.Builder} class. + * The following example shows a typical usage scenario:

              + * + *
              + * 
              + * BasicThreadFactory factory = new BasicThreadFactory.Builder()
              + *   .namingPattern("worker-thread-%d")
              + *   .daemon(true)
              + *   .uncaughtExceptionHandler(myHandler)
              + *   .build();
              + * ExecutorService exec = Executors.newSingleThreadExecutor(factory);
              + * 
              + * 
              + * + *

              The nested Builder class defines some methods for configuring the new BasicThreadFactory instance. + * Objects of this class are immutable, so these attributes cannot be changed later. + * The naming pattern is a string which can be passed to String.format(). + * The placeholder %d is replaced by an increasing counter value. + * An instance can wrap another ThreadFactory implementation; this is achieved by calling the builder's {@link com.fr.third.org.apache.commons.lang3.concurrent.BasicThreadFactory.Builder#wrappedFactory(java.util.concurrent.ThreadFactory) wrappedFactory(ThreadFactory)} method. + * This factory is then used for creating new threads; after that the specific attributes are applied to the new thread. + * If no wrapped factory is set, the default factory provided by the JDK is used.

              + * + *

              Synchronization objects

              + * + *

              The concurrent package also provides some support for specific synchronization problems with threads.

              + * + *

              {@link com.fr.third.org.apache.commons.lang3.concurrent.TimedSemaphore} allows restricted access to a resource in a given time frame. + * Similar to a semaphore, a number of permits can be acquired. + * What is new is the fact that the permits available are related to a given time unit. + * For instance, the timed semaphore can be configured to allow 10 permits in a second. + * Now multiple threads access the semaphore and call its {@link com.fr.third.org.apache.commons.lang3.concurrent.TimedSemaphore#acquire()} method. + * The semaphore keeps track about the number of granted permits in the current time frame. + * Only 10 calls are allowed; if there are further callers, they are blocked until the time frame (one second in this example) is over. + * Then all blocking threads are released, and the counter of available permits is reset to 0. + * So the game can start anew.

              + * + *

              What are use cases for TimedSemaphore? + * One example is to artificially limit the load produced by multiple threads. + * Consider a batch application accessing a database to extract statistical data. + * The application runs multiple threads which issue database queries in parallel and perform some calculation on the results. + * If the database to be processed is huge and is also used by a production system, multiple factors have to be balanced: + * On one hand, the time required for the statistical evaluation should not take too long. + * Therefore you will probably use a larger number of threads because most of its life time a thread will just wait for the database to return query results. + * On the other hand, the load on the database generated by all these threads should be limited so that the responsiveness of the production system is not affected. + * With a TimedSemaphore object this can be achieved. + * The semaphore can be configured to allow e.g. 100 queries per second. + * After these queries have been sent to the database the threads have to wait until the second is over - then they can query again. + * By fine-tuning the limit enforced by the semaphore a good balance between performance and database load can be established. + * It is even possible to chang? the number of available permits at runtime. + * So this number can be reduced during the typical working hours and increased at night.

              + * + *

              The following code examples demonstrate parts of the implementation of such a scenario. + * First the batch application has to create an instance of TimedSemaphore and to initialize its properties with default values:

              + * + * TimedSemaphore semaphore = new TimedSemaphore(1, TimeUnit.SECONDS, 100); + * + *

              Here we specify that the semaphore should allow 100 permits in one second. + * This is effectively the limit of database queries per second in our example use case. + * Next the server threads issuing database queries and performing statistical operations can be initialized. + * They are passed a reference to the semaphore at creation time. Before they execute a query they have to acquire a permit.

              + * + *
              + * 
              + * public class StatisticsTask implements Runnable {
              + * // The semaphore for limiting database load.
              + *   private final TimedSemaphore semaphore;
              + *
              + *   public StatisticsTask(TimedSemaphore sem, Connection con) {
              + *     semaphore = sem;
              + *      ...
              + *   }
              + *
              + *   //The main processing method. Executes queries and evaluates their results.
              + *   public void run() {
              + *     try {
              + *       while (!isDone()) {
              + *         semaphore.acquire();    // enforce the load limit
              + *         executeAndEvaluateQuery();
              + *       }
              + *     } catch (InterruptedException iex) {
              + *       // fall through
              + *     }
              + *   }
              + * }
              + * 
              + * 
              + * + *

              The important line here is the call to semaphore.acquire(). + * If the number of permits in the current time frame has not yet been reached, the call returns immediately. + * Otherwise, it blocks until the end of the time frame. + * The last piece missing is a scheduler service which adapts the number of permits allowed by the semaphore according to the time of day. + * We assume that this service is pretty simple and knows only two different time slots: + * working shift and night shift. + * The service is triggered periodically. + * It then determines the current time slot and configures the timed semaphore accordingly.

              + * + *
              + * 
              + * public class SchedulerService {
              + *   // The semaphore for limiting database load.
              + *   private final TimedSemaphore semaphore;
              + *     ...
              + *
              + *   // Configures the timed semaphore based on the current time of day. This method is called periodically.
              + *   public void configureTimedSemaphore() {
              + *      int limit;
              + *      if (isWorkshift()) {
              + *        limit = 50;    // low database load
              + *      } else {
              + *        limit = 250;   // high database load
              + *      }
              + *
              + *      semaphore.setLimit(limit);
              + *   }
              + * }
              + * 
              + * 
              + * + *

              With the {@link com.fr.third.org.apache.commons.lang3.concurrent.TimedSemaphore#setLimit(int)} method the number of permits allowed for a time frame can be changed. + * There are some other methods for querying the internal state of a timed semaphore. + * Also some statistical data is available, e.g. the average number of acquire() calls per time frame. + * When a timed semaphore is no more needed, its shutdown() method has to be called.

              + * + * @version $Id: package-info.java 1583482 2014-03-31 22:54:57Z niallp $ + */ +package com.fr.third.org.apache.commons.lang3.concurrent; diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/EventListenerSupport.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/EventListenerSupport.java new file mode 100644 index 000000000..1c1e299f4 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/EventListenerSupport.java @@ -0,0 +1,322 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.lang3.event; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import com.fr.third.org.apache.commons.lang3.Validate; + +/** + *

              An EventListenerSupport object can be used to manage a list of event + * listeners of a particular type. The class provides + * {@link #addListener(Object)} and {@link #removeListener(Object)} methods + * for registering listeners, as well as a {@link #fire()} method for firing + * events to the listeners. + *

              + * + *

              + * To use this class, suppose you want to support ActionEvents. You would do: + *

              + *
              
              + * public class MyActionEventSource
              + * {
              + *   private EventListenerSupport<ActionListener> actionListeners =
              + *       EventListenerSupport.create(ActionListener.class);
              + *
              + *   public void someMethodThatFiresAction()
              + *   {
              + *     ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
              + *     actionListeners.fire().actionPerformed(e);
              + *   }
              + * }
              + * 
              + * + *

              + * Serializing an {@link EventListenerSupport} instance will result in any + * non-{@link Serializable} listeners being silently dropped. + *

              + * + * @param the type of event listener that is supported by this proxy. + * + * @since 3.0 + * @version $Id: EventListenerSupport.java 1583482 2014-03-31 22:54:57Z niallp $ + */ +public class EventListenerSupport implements Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 3593265990380473632L; + + /** + * The list used to hold the registered listeners. This list is + * intentionally a thread-safe copy-on-write-array so that traversals over + * the list of listeners will be atomic. + */ + private List listeners = new CopyOnWriteArrayList(); + + /** + * The proxy representing the collection of listeners. Calls to this proxy + * object will sent to all registered listeners. + */ + private transient L proxy; + + /** + * Empty typed array for #getListeners(). + */ + private transient L[] prototypeArray; + + /** + * Creates an EventListenerSupport object which supports the specified + * listener type. + * + * @param the type of the listener interface + * @param listenerInterface the type of listener interface that will receive + * events posted using this class. + * + * @return an EventListenerSupport object which supports the specified + * listener type. + * + * @throws NullPointerException if listenerInterface is + * null. + * @throws IllegalArgumentException if listenerInterface is + * not an interface. + */ + public static EventListenerSupport create(final Class listenerInterface) { + return new EventListenerSupport(listenerInterface); + } + + /** + * Creates an EventListenerSupport object which supports the provided + * listener interface. + * + * @param listenerInterface the type of listener interface that will receive + * events posted using this class. + * + * @throws NullPointerException if listenerInterface is + * null. + * @throws IllegalArgumentException if listenerInterface is + * not an interface. + */ + public EventListenerSupport(final Class listenerInterface) { + this(listenerInterface, Thread.currentThread().getContextClassLoader()); + } + + /** + * Creates an EventListenerSupport object which supports the provided + * listener interface using the specified class loader to create the JDK + * dynamic proxy. + * + * @param listenerInterface the listener interface. + * @param classLoader the class loader. + * + * @throws NullPointerException if listenerInterface or + * classLoader is null. + * @throws IllegalArgumentException if listenerInterface is + * not an interface. + */ + public EventListenerSupport(final Class listenerInterface, final ClassLoader classLoader) { + this(); + Validate.notNull(listenerInterface, "Listener interface cannot be null."); + Validate.notNull(classLoader, "ClassLoader cannot be null."); + Validate.isTrue(listenerInterface.isInterface(), "Class {0} is not an interface", + listenerInterface.getName()); + initializeTransientFields(listenerInterface, classLoader); + } + + /** + * Create a new EventListenerSupport instance. + * Serialization-friendly constructor. + */ + private EventListenerSupport() { + } + + /** + * Returns a proxy object which can be used to call listener methods on all + * of the registered event listeners. All calls made to this proxy will be + * forwarded to all registered listeners. + * + * @return a proxy object which can be used to call listener methods on all + * of the registered event listeners + */ + public L fire() { + return proxy; + } + +//********************************************************************************************************************** +// Other Methods +//********************************************************************************************************************** + + /** + * Registers an event listener. + * + * @param listener the event listener (may not be null). + * + * @throws NullPointerException if listener is + * null. + */ + public void addListener(final L listener) { + Validate.notNull(listener, "Listener object cannot be null."); + listeners.add(listener); + } + + /** + * Returns the number of registered listeners. + * + * @return the number of registered listeners. + */ + int getListenerCount() { + return listeners.size(); + } + + /** + * Unregisters an event listener. + * + * @param listener the event listener (may not be null). + * + * @throws NullPointerException if listener is + * null. + */ + public void removeListener(final L listener) { + Validate.notNull(listener, "Listener object cannot be null."); + listeners.remove(listener); + } + + /** + * Get an array containing the currently registered listeners. + * Modification to this array's elements will have no effect on the + * {@link EventListenerSupport} instance. + * @return L[] + */ + public L[] getListeners() { + return listeners.toArray(prototypeArray); + } + + /** + * Serialize. + * @param objectOutputStream the output stream + * @throws IOException if an IO error occurs + */ + private void writeObject(final ObjectOutputStream objectOutputStream) throws IOException { + final ArrayList serializableListeners = new ArrayList(); + + // don't just rely on instanceof Serializable: + ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream()); + for (final L listener : listeners) { + try { + testObjectOutputStream.writeObject(listener); + serializableListeners.add(listener); + } catch (final IOException exception) { + //recreate test stream in case of indeterminate state + testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream()); + } + } + /* + * we can reconstitute everything we need from an array of our listeners, + * which has the additional advantage of typically requiring less storage than a list: + */ + objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray)); + } + + /** + * Deserialize. + * @param objectInputStream the input stream + * @throws IOException if an IO error occurs + * @throws ClassNotFoundException if the class cannot be resolved + */ + private void readObject(final ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException { + @SuppressWarnings("unchecked") // Will throw CCE here if not correct + final + L[] srcListeners = (L[]) objectInputStream.readObject(); + + this.listeners = new CopyOnWriteArrayList(srcListeners); + + @SuppressWarnings("unchecked") // Will throw CCE here if not correct + final + Class listenerInterface = (Class) srcListeners.getClass().getComponentType(); + + initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader()); + } + + /** + * Initialize transient fields. + * @param listenerInterface the class of the listener interface + * @param classLoader the class loader to be used + */ + private void initializeTransientFields(final Class listenerInterface, final ClassLoader classLoader) { + @SuppressWarnings("unchecked") // Will throw CCE here if not correct + final + L[] array = (L[]) Array.newInstance(listenerInterface, 0); + this.prototypeArray = array; + createProxy(listenerInterface, classLoader); + } + + /** + * Create the proxy object. + * @param listenerInterface the class of the listener interface + * @param classLoader the class loader to be used + */ + private void createProxy(final Class listenerInterface, final ClassLoader classLoader) { + proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, + new Class[] { listenerInterface }, createInvocationHandler())); + } + + /** + * Create the {@link InvocationHandler} responsible for broadcasting calls + * to the managed listeners. Subclasses can override to provide custom behavior. + * @return ProxyInvocationHandler + */ + protected InvocationHandler createInvocationHandler() { + return new ProxyInvocationHandler(); + } + + /** + * An invocation handler used to dispatch the event(s) to all the listeners. + */ + protected class ProxyInvocationHandler implements InvocationHandler { + + /** + * Propagates the method call to all registered listeners in place of + * the proxy listener object. + * + * @param unusedProxy the proxy object representing a listener on which the + * invocation was called; not used + * @param method the listener method that will be called on all of the + * listeners. + * @param args event arguments to propagate to the listeners. + * @return the result of the method call + * @throws Throwable if an error occurs + */ + @Override + public Object invoke(final Object unusedProxy, final Method method, final Object[] args) throws Throwable { + for (final L listener : listeners) { + method.invoke(listener, args); + } + return null; + } + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/EventUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/EventUtils.java new file mode 100644 index 000000000..8b53128fb --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/EventUtils.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.lang3.event; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import com.fr.third.org.apache.commons.lang3.reflect.MethodUtils; + +/** + * Provides some useful event-based utility methods. + * + * @since 3.0 + * @version $Id: EventUtils.java 1606082 2014-06-27 13:03:36Z ggregory $ + */ +public class EventUtils { + + /** + * Adds an event listener to the specified source. This looks for an "add" method corresponding to the event + * type (addActionListener, for example). + * @param eventSource the event source + * @param listenerType the event listener type + * @param listener the listener + * @param the event listener type + * + * @throws IllegalArgumentException if the object doesn't support the listener type + */ + public static void addEventListener(final Object eventSource, final Class listenerType, final L listener) { + try { + MethodUtils.invokeMethod(eventSource, "add" + listenerType.getSimpleName(), listener); + } catch (final NoSuchMethodException e) { + throw new IllegalArgumentException("Class " + eventSource.getClass().getName() + + " does not have a public add" + listenerType.getSimpleName() + + " method which takes a parameter of type " + listenerType.getName() + "."); + } catch (final IllegalAccessException e) { + throw new IllegalArgumentException("Class " + eventSource.getClass().getName() + + " does not have an accessible add" + listenerType.getSimpleName () + + " method which takes a parameter of type " + listenerType.getName() + "."); + } catch (final InvocationTargetException e) { + throw new RuntimeException("Unable to add listener.", e.getCause()); + } + } + + /** + * Binds an event listener to a specific method on a specific object. + * + * @param the event listener type + * @param target the target object + * @param methodName the name of the method to be called + * @param eventSource the object which is generating events (JButton, JList, etc.) + * @param listenerType the listener interface (ActionListener.class, SelectionListener.class, etc.) + * @param eventTypes the event types (method names) from the listener interface (if none specified, all will be + * supported) + */ + public static void bindEventsToMethod(final Object target, final String methodName, final Object eventSource, + final Class listenerType, final String... eventTypes) { + final L listener = listenerType.cast(Proxy.newProxyInstance(target.getClass().getClassLoader(), + new Class[] { listenerType }, new EventBindingInvocationHandler(target, methodName, eventTypes))); + addEventListener(eventSource, listenerType, listener); + } + + private static class EventBindingInvocationHandler implements InvocationHandler { + private final Object target; + private final String methodName; + private final Set eventTypes; + + /** + * Creates a new instance of {@code EventBindingInvocationHandler}. + * + * @param target the target object for method invocations + * @param methodName the name of the method to be invoked + * @param eventTypes the names of the supported event types + */ + EventBindingInvocationHandler(final Object target, final String methodName, final String[] eventTypes) { + this.target = target; + this.methodName = methodName; + this.eventTypes = new HashSet(Arrays.asList(eventTypes)); + } + + /** + * Handles a method invocation on the proxy object. + * + * @param proxy the proxy instance + * @param method the method to be invoked + * @param parameters the parameters for the method invocation + * @return the result of the method call + * @throws Throwable if an error occurs + */ + @Override + public Object invoke(final Object proxy, final Method method, final Object[] parameters) throws Throwable { + if (eventTypes.isEmpty() || eventTypes.contains(method.getName())) { + if (hasMatchingParametersMethod(method)) { + return MethodUtils.invokeMethod(target, methodName, parameters); + } + return MethodUtils.invokeMethod(target, methodName); + } + return null; + } + + /** + * Checks whether a method for the passed in parameters can be found. + * + * @param method the listener method invoked + * @return a flag whether the parameters could be matched + */ + private boolean hasMatchingParametersMethod(final Method method) { + return MethodUtils.getAccessibleMethod(target.getClass(), methodName, method.getParameterTypes()) != null; + } + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/package-info.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/package-info.java new file mode 100644 index 000000000..4e05309c8 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/event/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Provides some useful event-based utilities. + * + * @since 3.0 + * @version $Id: package-info.java 1558546 2014-01-15 19:38:15Z britter $ + */ +package com.fr.third.org.apache.commons.lang3.event; diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/CloneFailedException.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/CloneFailedException.java new file mode 100644 index 000000000..e8694af61 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/CloneFailedException.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.exception; + +/** + * Exception thrown when a clone cannot be created. In contrast to + * {@link CloneNotSupportedException} this is a {@link RuntimeException}. + * + * @since 3.0 + */ +public class CloneFailedException extends RuntimeException { + // ~ Static fields/initializers --------------------------------------------- + + private static final long serialVersionUID = 20091223L; + + // ~ Constructors ----------------------------------------------------------- + + /** + * Constructs a CloneFailedException. + * + * @param message description of the exception + * @since upcoming + */ + public CloneFailedException(final String message) { + super(message); + } + + /** + * Constructs a CloneFailedException. + * + * @param cause cause of the exception + * @since upcoming + */ + public CloneFailedException(final Throwable cause) { + super(cause); + } + + /** + * Constructs a CloneFailedException. + * + * @param message description of the exception + * @param cause cause of the exception + * @since upcoming + */ + public CloneFailedException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ContextedException.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ContextedException.java new file mode 100644 index 000000000..3504e113d --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ContextedException.java @@ -0,0 +1,256 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.exception; + +import java.util.List; +import java.util.Set; + +import com.fr.third.org.apache.commons.lang3.tuple.Pair; + +/** + *

              + * An exception that provides an easy and safe way to add contextual information. + *

              + * An exception trace itself is often insufficient to provide rapid diagnosis of the issue. + * Frequently what is needed is a select few pieces of local contextual data. + * Providing this data is tricky however, due to concerns over formatting and nulls. + *

              + * The contexted exception approach allows the exception to be created together with a + * list of context label-value pairs. This additional information is automatically included in + * the message and printed stack trace. + *

              + * An unchecked version of this exception is provided by ContextedRuntimeException. + *

              + *

              + * To use this class write code as follows: + *

              + *
              + *   try {
              + *     ...
              + *   } catch (Exception e) {
              + *     throw new ContextedException("Error posting account transaction", e)
              + *          .addContextValue("Account Number", accountNumber)
              + *          .addContextValue("Amount Posted", amountPosted)
              + *          .addContextValue("Previous Balance", previousBalance)
              + *   }
              + * }
              + * 
              + *

              + * or improve diagnose data at a higher level: + *

              + *
              + *   try {
              + *     ...
              + *   } catch (ContextedException e) {
              + *     throw e.setContextValue("Transaction Id", transactionId);
              + *   } catch (Exception e) {
              + *     if (e instanceof ExceptionContext) {
              + *       e.setContextValue("Transaction Id", transactionId);
              + *     }
              + *     throw e;
              + *   }
              + * }
              + * 
              + *

              + * The output in a printStacktrace() (which often is written to a log) would look something like the following: + *

              + *
              + * org.apache.commons.lang3.exception.ContextedException: java.lang.Exception: Error posting account transaction
              + *  Exception Context:
              + *  [1:Account Number=null]
              + *  [2:Amount Posted=100.00]
              + *  [3:Previous Balance=-2.17]
              + *  [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
              + *
              + *  ---------------------------------
              + *  at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88)
              + *  ..... (rest of trace)
              + * 
              + * + * @see ContextedRuntimeException + * @since 3.0 + */ +public class ContextedException extends Exception implements ExceptionContext { + + /** The serialization version. */ + private static final long serialVersionUID = 20110706L; + /** The context where the data is stored. */ + private final ExceptionContext exceptionContext; + + /** + * Instantiates ContextedException without message or cause. + *

              + * The context information is stored using a default implementation. + */ + public ContextedException() { + super(); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedException with message, but without cause. + *

              + * The context information is stored using a default implementation. + * + * @param message the exception message, may be null + */ + public ContextedException(final String message) { + super(message); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedException with cause, but without message. + *

              + * The context information is stored using a default implementation. + * + * @param cause the underlying cause of the exception, may be null + */ + public ContextedException(final Throwable cause) { + super(cause); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedException with cause and message. + *

              + * The context information is stored using a default implementation. + * + * @param message the exception message, may be null + * @param cause the underlying cause of the exception, may be null + */ + public ContextedException(final String message, final Throwable cause) { + super(message, cause); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedException with cause, message, and ExceptionContext. + * + * @param message the exception message, may be null + * @param cause the underlying cause of the exception, may be null + * @param context the context used to store the additional information, null uses default implementation + */ + public ContextedException(final String message, final Throwable cause, ExceptionContext context) { + super(message, cause); + if (context == null) { + context = new DefaultExceptionContext(); + } + exceptionContext = context; + } + + //----------------------------------------------------------------------- + /** + * Adds information helpful to a developer in diagnosing and correcting the problem. + * For the information to be meaningful, the value passed should have a reasonable + * toString() implementation. + * Different values can be added with the same label multiple times. + *

              + * Note: This exception is only serializable if the object added is serializable. + *

              + * + * @param label a textual label associated with information, {@code null} not recommended + * @param value information needed to understand exception, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + @Override + public ContextedException addContextValue(final String label, final Object value) { + exceptionContext.addContextValue(label, value); + return this; + } + + /** + * Sets information helpful to a developer in diagnosing and correcting the problem. + * For the information to be meaningful, the value passed should have a reasonable + * toString() implementation. + * Any existing values with the same labels are removed before the new one is added. + *

              + * Note: This exception is only serializable if the object added as value is serializable. + *

              + * + * @param label a textual label associated with information, {@code null} not recommended + * @param value information needed to understand exception, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + @Override + public ContextedException setContextValue(final String label, final Object value) { + exceptionContext.setContextValue(label, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List getContextValues(final String label) { + return this.exceptionContext.getContextValues(label); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getFirstContextValue(final String label) { + return this.exceptionContext.getFirstContextValue(label); + } + + /** + * {@inheritDoc} + */ + @Override + public List> getContextEntries() { + return this.exceptionContext.getContextEntries(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getContextLabels() { + return exceptionContext.getContextLabels(); + } + + /** + * Provides the message explaining the exception, including the contextual data. + * + * @see java.lang.Throwable#getMessage() + * @return the message, never null + */ + @Override + public String getMessage(){ + return getFormattedExceptionMessage(super.getMessage()); + } + + /** + * Provides the message explaining the exception without the contextual data. + * + * @see java.lang.Throwable#getMessage() + * @return the message + * @since 3.0.1 + */ + public String getRawMessage() { + return super.getMessage(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getFormattedExceptionMessage(final String baseMessage) { + return exceptionContext.getFormattedExceptionMessage(baseMessage); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ContextedRuntimeException.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ContextedRuntimeException.java new file mode 100644 index 000000000..62e4e7bba --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ContextedRuntimeException.java @@ -0,0 +1,257 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.exception; + +import java.util.List; +import java.util.Set; + +import com.fr.third.org.apache.commons.lang3.tuple.Pair; + +/** + *

              + * A runtime exception that provides an easy and safe way to add contextual information. + *

              + * An exception trace itself is often insufficient to provide rapid diagnosis of the issue. + * Frequently what is needed is a select few pieces of local contextual data. + * Providing this data is tricky however, due to concerns over formatting and nulls. + *

              + * The contexted exception approach allows the exception to be created together with a + * list of context label-value pairs. This additional information is automatically included in + * the message and printed stack trace. + *

              + * A checked version of this exception is provided by ContextedException. + *

              + *

              + * To use this class write code as follows: + *

              + *
              + *   try {
              + *     ...
              + *   } catch (Exception e) {
              + *     throw new ContextedRuntimeException("Error posting account transaction", e)
              + *          .addContextValue("Account Number", accountNumber)
              + *          .addContextValue("Amount Posted", amountPosted)
              + *          .addContextValue("Previous Balance", previousBalance)
              + *   }
              + * }
              + * 
              + *

              + * or improve diagnose data at a higher level: + *

              + *
              + *   try {
              + *     ...
              + *   } catch (ContextedRuntimeException e) {
              + *     throw e.setContextValue("Transaction Id", transactionId);
              + *   } catch (Exception e) {
              + *     if (e instanceof ExceptionContext) {
              + *       e.setContextValue("Transaction Id", transactionId);
              + *     }
              + *     throw e;
              + *   }
              + * }
              + * 
              + *

              + * The output in a printStacktrace() (which often is written to a log) would look something like the following: + *

              + *
              + * org.apache.commons.lang3.exception.ContextedRuntimeException: java.lang.Exception: Error posting account transaction
              + *  Exception Context:
              + *  [1:Account Number=null]
              + *  [2:Amount Posted=100.00]
              + *  [3:Previous Balance=-2.17]
              + *  [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
              + *
              + *  ---------------------------------
              + *  at org.apache.commons.lang3.exception.ContextedRuntimeExceptionTest.testAddValue(ContextedExceptionTest.java:88)
              + *  ..... (rest of trace)
              + * 
              + * + * @see ContextedException + * @since 3.0 + */ +public class ContextedRuntimeException extends RuntimeException implements ExceptionContext { + + /** The serialization version. */ + private static final long serialVersionUID = 20110706L; + /** The context where the data is stored. */ + private final ExceptionContext exceptionContext; + + /** + * Instantiates ContextedRuntimeException without message or cause. + *

              + * The context information is stored using a default implementation. + */ + public ContextedRuntimeException() { + super(); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedRuntimeException with message, but without cause. + *

              + * The context information is stored using a default implementation. + * + * @param message the exception message, may be null + */ + public ContextedRuntimeException(final String message) { + super(message); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedRuntimeException with cause, but without message. + *

              + * The context information is stored using a default implementation. + * + * @param cause the underlying cause of the exception, may be null + */ + public ContextedRuntimeException(final Throwable cause) { + super(cause); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedRuntimeException with cause and message. + *

              + * The context information is stored using a default implementation. + * + * @param message the exception message, may be null + * @param cause the underlying cause of the exception, may be null + */ + public ContextedRuntimeException(final String message, final Throwable cause) { + super(message, cause); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedRuntimeException with cause, message, and ExceptionContext. + * + * @param message the exception message, may be null + * @param cause the underlying cause of the exception, may be null + * @param context the context used to store the additional information, null uses default implementation + */ + public ContextedRuntimeException(final String message, final Throwable cause, ExceptionContext context) { + super(message, cause); + if (context == null) { + context = new DefaultExceptionContext(); + } + exceptionContext = context; + } + + //----------------------------------------------------------------------- + /** + * Adds information helpful to a developer in diagnosing and correcting the problem. + * For the information to be meaningful, the value passed should have a reasonable + * toString() implementation. + * Different values can be added with the same label multiple times. + *

              + * Note: This exception is only serializable if the object added is serializable. + *

              + * + * @param label a textual label associated with information, {@code null} not recommended + * @param value information needed to understand exception, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + @Override + public ContextedRuntimeException addContextValue(final String label, final Object value) { + exceptionContext.addContextValue(label, value); + return this; + } + + /** + * Sets information helpful to a developer in diagnosing and correcting the problem. + * For the information to be meaningful, the value passed should have a reasonable + * toString() implementation. + * Any existing values with the same labels are removed before the new one is added. + *

              + * Note: This exception is only serializable if the object added as value is serializable. + *

              + * + * @param label a textual label associated with information, {@code null} not recommended + * @param value information needed to understand exception, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + @Override + public ContextedRuntimeException setContextValue(final String label, final Object value) { + exceptionContext.setContextValue(label, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List getContextValues(final String label) { + return this.exceptionContext.getContextValues(label); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getFirstContextValue(final String label) { + return this.exceptionContext.getFirstContextValue(label); + } + + /** + * {@inheritDoc} + */ + @Override + public List> getContextEntries() { + return this.exceptionContext.getContextEntries(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getContextLabels() { + return exceptionContext.getContextLabels(); + } + + /** + * Provides the message explaining the exception, including the contextual data. + * + * @see java.lang.Throwable#getMessage() + * @return the message, never null + */ + @Override + public String getMessage(){ + return getFormattedExceptionMessage(super.getMessage()); + } + + /** + * Provides the message explaining the exception without the contextual data. + * + * @see java.lang.Throwable#getMessage() + * @return the message + * @since 3.0.1 + */ + public String getRawMessage() { + return super.getMessage(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getFormattedExceptionMessage(final String baseMessage) { + return exceptionContext.getFormattedExceptionMessage(baseMessage); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/DefaultExceptionContext.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/DefaultExceptionContext.java new file mode 100644 index 000000000..bdbb0165e --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/DefaultExceptionContext.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.exception; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import com.fr.third.org.apache.commons.lang3.StringUtils; +import com.fr.third.org.apache.commons.lang3.tuple.ImmutablePair; +import com.fr.third.org.apache.commons.lang3.tuple.Pair; + +/** + * Default implementation of the context storing the label-value pairs for contexted exceptions. + *

              + * This implementation is serializable, however this is dependent on the values that + * are added also being serializable. + *

              + * + * @see ContextedException + * @see ContextedRuntimeException + * @since 3.0 + */ +public class DefaultExceptionContext implements ExceptionContext, Serializable { + + /** The serialization version. */ + private static final long serialVersionUID = 20110706L; + + /** The list storing the label-data pairs. */ + private final List> contextValues = new ArrayList>(); + + /** + * {@inheritDoc} + */ + @Override + public DefaultExceptionContext addContextValue(final String label, final Object value) { + contextValues.add(new ImmutablePair(label, value)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public DefaultExceptionContext setContextValue(final String label, final Object value) { + for (final Iterator> iter = contextValues.iterator(); iter.hasNext();) { + final Pair p = iter.next(); + if (StringUtils.equals(label, p.getKey())) { + iter.remove(); + } + } + addContextValue(label, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List getContextValues(final String label) { + final List values = new ArrayList(); + for (final Pair pair : contextValues) { + if (StringUtils.equals(label, pair.getKey())) { + values.add(pair.getValue()); + } + } + return values; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getFirstContextValue(final String label) { + for (final Pair pair : contextValues) { + if (StringUtils.equals(label, pair.getKey())) { + return pair.getValue(); + } + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getContextLabels() { + final Set labels = new HashSet(); + for (final Pair pair : contextValues) { + labels.add(pair.getKey()); + } + return labels; + } + + /** + * {@inheritDoc} + */ + @Override + public List> getContextEntries() { + return contextValues; + } + + /** + * Builds the message containing the contextual information. + * + * @param baseMessage the base exception message without context information appended + * @return the exception message with context information appended, never null + */ + @Override + public String getFormattedExceptionMessage(final String baseMessage){ + final StringBuilder buffer = new StringBuilder(256); + if (baseMessage != null) { + buffer.append(baseMessage); + } + + if (contextValues.size() > 0) { + if (buffer.length() > 0) { + buffer.append('\n'); + } + buffer.append("Exception Context:\n"); + + int i = 0; + for (final Pair pair : contextValues) { + buffer.append("\t["); + buffer.append(++i); + buffer.append(':'); + buffer.append(pair.getKey()); + buffer.append("="); + final Object value = pair.getValue(); + if (value == null) { + buffer.append("null"); + } else { + String valueStr; + try { + valueStr = value.toString(); + } catch (final Exception e) { + valueStr = "Exception thrown on toString(): " + ExceptionUtils.getStackTrace(e); + } + buffer.append(valueStr); + } + buffer.append("]\n"); + } + buffer.append("---------------------------------"); + } + return buffer.toString(); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ExceptionContext.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ExceptionContext.java new file mode 100644 index 000000000..72374d788 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ExceptionContext.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.exception; + +import java.util.List; +import java.util.Set; + +import com.fr.third.org.apache.commons.lang3.tuple.Pair; + +/** + * Allows the storage and retrieval of contextual information based on label-value + * pairs for exceptions. + *

              + * Implementations are expected to manage the pairs in a list-style collection + * that keeps the pairs in the sequence of their addition. + *

              + * + * @see ContextedException + * @see ContextedRuntimeException + * @since 3.0 + */ +public interface ExceptionContext { + + /** + * Adds a contextual label-value pair into this context. + *

              + * The pair will be added to the context, independently of an already + * existing pair with the same label. + *

              + * + * @param label the label of the item to add, {@code null} not recommended + * @param value the value of item to add, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + ExceptionContext addContextValue(String label, Object value); + + /** + * Sets a contextual label-value pair into this context. + *

              + * The pair will be added normally, but any existing label-value pair with + * the same label is removed from the context. + *

              + * + * @param label the label of the item to add, {@code null} not recommended + * @param value the value of item to add, may be {@code null} + * @return {@code this}, for method chaining, not {@code null} + */ + ExceptionContext setContextValue(String label, Object value); + + /** + * Retrieves all the contextual data values associated with the label. + * + * @param label the label to get the contextual values for, may be {@code null} + * @return the contextual values associated with the label, never {@code null} + */ + List getContextValues(String label); + + /** + * Retrieves the first available contextual data value associated with the label. + * + * @param label the label to get the contextual value for, may be {@code null} + * @return the first contextual value associated with the label, may be {@code null} + */ + Object getFirstContextValue(String label); + + /** + * Retrieves the full set of labels defined in the contextual data. + * + * @return the set of labels, not {@code null} + */ + Set getContextLabels(); + + /** + * Retrieves the full list of label-value pairs defined in the contextual data. + * + * @return the list of pairs, not {@code null} + */ + List> getContextEntries(); + + /** + * Gets the contextualized error message based on a base message. + * This will add the context label-value pairs to the message. + * + * @param baseMessage the base exception message without context information appended + * @return the exception message with context information appended, not {@code null} + */ + String getFormattedExceptionMessage(String baseMessage); + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ExceptionUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ExceptionUtils.java new file mode 100644 index 000000000..0c0bc8991 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/ExceptionUtils.java @@ -0,0 +1,697 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.exception; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; +import com.fr.third.org.apache.commons.lang3.ClassUtils; +import com.fr.third.org.apache.commons.lang3.StringUtils; +import com.fr.third.org.apache.commons.lang3.SystemUtils; + +/** + *

              Provides utilities for manipulating and examining + * Throwable objects.

              + * + * @since 1.0 + * @version $Id: ExceptionUtils.java 1436770 2013-01-22 07:09:45Z ggregory $ + */ +public class ExceptionUtils { + + /** + *

              Used when printing stack frames to denote the start of a + * wrapped exception.

              + * + *

              Package private for accessibility by test suite.

              + */ + static final String WRAPPED_MARKER = " [wrapped] "; + + /** + *

              The names of methods commonly used to access a wrapped exception.

              + */ + // TODO: Remove in Lang 4.0 + private static final String[] CAUSE_METHOD_NAMES = { + "getCause", + "getNextException", + "getTargetException", + "getException", + "getSourceException", + "getRootCause", + "getCausedByException", + "getNested", + "getLinkedException", + "getNestedException", + "getLinkedCause", + "getThrowable", + }; + + /** + *

              + * Public constructor allows an instance of ExceptionUtils to be created, although that is not + * normally necessary. + *

              + */ + public ExceptionUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + *

              Returns the default names used when searching for the cause of an exception.

              + * + *

              This may be modified and used in the overloaded getCause(Throwable, String[]) method.

              + * + * @return cloned array of the default method names + * @since 3.0 + * @deprecated This feature will be removed in Lang 4.0 + */ + @Deprecated + public static String[] getDefaultCauseMethodNames() { + return ArrayUtils.clone(CAUSE_METHOD_NAMES); + } + + //----------------------------------------------------------------------- + /** + *

              Introspects the Throwable to obtain the cause.

              + * + *

              The method searches for methods with specific names that return a + * Throwable object. This will pick up most wrapping exceptions, + * including those from JDK 1.4. + * + *

              The default list searched for are:

              + *
                + *
              • getCause()
              • + *
              • getNextException()
              • + *
              • getTargetException()
              • + *
              • getException()
              • + *
              • getSourceException()
              • + *
              • getRootCause()
              • + *
              • getCausedByException()
              • + *
              • getNested()
              • + *
              + * + *

              If none of the above is found, returns null.

              + * + * @param throwable the throwable to introspect for a cause, may be null + * @return the cause of the Throwable, + * null if none found or null throwable input + * @since 1.0 + * @deprecated This feature will be removed in Lang 4.0 + */ + @Deprecated + public static Throwable getCause(final Throwable throwable) { + return getCause(throwable, CAUSE_METHOD_NAMES); + } + + /** + *

              Introspects the Throwable to obtain the cause.

              + * + *

              A null set of method names means use the default set. + * A null in the set of method names will be ignored.

              + * + * @param throwable the throwable to introspect for a cause, may be null + * @param methodNames the method names, null treated as default set + * @return the cause of the Throwable, + * null if none found or null throwable input + * @since 1.0 + * @deprecated This feature will be removed in Lang 4.0 + */ + @Deprecated + public static Throwable getCause(final Throwable throwable, String[] methodNames) { + if (throwable == null) { + return null; + } + + if (methodNames == null) { + methodNames = CAUSE_METHOD_NAMES; + } + + for (final String methodName : methodNames) { + if (methodName != null) { + final Throwable cause = getCauseUsingMethodName(throwable, methodName); + if (cause != null) { + return cause; + } + } + } + + return null; + } + + /** + *

              Introspects the Throwable to obtain the root cause.

              + * + *

              This method walks through the exception chain to the last element, + * "root" of the tree, using {@link #getCause(Throwable)}, and + * returns that exception.

              + * + *

              From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. If the throwable parameter + * has a cause of itself, then null will be returned. If the throwable + * parameter cause chain loops, the last element in the chain before the + * loop is returned.

              + * + * @param throwable the throwable to get the root cause for, may be null + * @return the root cause of the Throwable, + * null if none found or null throwable input + */ + public static Throwable getRootCause(final Throwable throwable) { + final List list = getThrowableList(throwable); + return list.size() < 2 ? null : (Throwable)list.get(list.size() - 1); + } + + /** + *

              Finds a Throwable by method name.

              + * + * @param throwable the exception to examine + * @param methodName the name of the method to find and invoke + * @return the wrapped exception, or null if not found + */ + // TODO: Remove in Lang 4.0 + private static Throwable getCauseUsingMethodName(final Throwable throwable, final String methodName) { + Method method = null; + try { + method = throwable.getClass().getMethod(methodName); + } catch (final NoSuchMethodException ignored) { // NOPMD + // exception ignored + } catch (final SecurityException ignored) { // NOPMD + // exception ignored + } + + if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) { + try { + return (Throwable) method.invoke(throwable); + } catch (final IllegalAccessException ignored) { // NOPMD + // exception ignored + } catch (final IllegalArgumentException ignored) { // NOPMD + // exception ignored + } catch (final InvocationTargetException ignored) { // NOPMD + // exception ignored + } + } + return null; + } + + //----------------------------------------------------------------------- + /** + *

              Counts the number of Throwable objects in the + * exception chain.

              + * + *

              A throwable without cause will return 1. + * A throwable with one cause will return 2 and so on. + * A null throwable will return 0.

              + * + *

              From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. The cause chain is + * processed until the end is reached, or until the next item in the + * chain is already in the result set.

              + * + * @param throwable the throwable to inspect, may be null + * @return the count of throwables, zero if null input + */ + public static int getThrowableCount(final Throwable throwable) { + return getThrowableList(throwable).size(); + } + + /** + *

              Returns the list of Throwable objects in the + * exception chain.

              + * + *

              A throwable without cause will return an array containing + * one element - the input throwable. + * A throwable with one cause will return an array containing + * two elements. - the input throwable and the cause throwable. + * A null throwable will return an array of size zero.

              + * + *

              From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. The cause chain is + * processed until the end is reached, or until the next item in the + * chain is already in the result set.

              + * + * @see #getThrowableList(Throwable) + * @param throwable the throwable to inspect, may be null + * @return the array of throwables, never null + */ + public static Throwable[] getThrowables(final Throwable throwable) { + final List list = getThrowableList(throwable); + return list.toArray(new Throwable[list.size()]); + } + + /** + *

              Returns the list of Throwable objects in the + * exception chain.

              + * + *

              A throwable without cause will return a list containing + * one element - the input throwable. + * A throwable with one cause will return a list containing + * two elements. - the input throwable and the cause throwable. + * A null throwable will return a list of size zero.

              + * + *

              This method handles recursive cause structures that might + * otherwise cause infinite loops. The cause chain is processed until + * the end is reached, or until the next item in the chain is already + * in the result set.

              + * + * @param throwable the throwable to inspect, may be null + * @return the list of throwables, never null + * @since Commons Lang 2.2 + */ + public static List getThrowableList(Throwable throwable) { + final List list = new ArrayList(); + while (throwable != null && list.contains(throwable) == false) { + list.add(throwable); + throwable = ExceptionUtils.getCause(throwable); + } + return list; + } + + //----------------------------------------------------------------------- + /** + *

              Returns the (zero based) index of the first Throwable + * that matches the specified class (exactly) in the exception chain. + * Subclasses of the specified class do not match - see + * {@link #indexOfType(Throwable, Class)} for the opposite.

              + * + *

              A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1.

              + * + * @param throwable the throwable to inspect, may be null + * @param clazz the class to search for, subclasses do not match, null returns -1 + * @return the index into the throwable chain, -1 if no match or null input + */ + public static int indexOfThrowable(final Throwable throwable, final Class clazz) { + return indexOf(throwable, clazz, 0, false); + } + + /** + *

              Returns the (zero based) index of the first Throwable + * that matches the specified type in the exception chain from + * a specified index. + * Subclasses of the specified class do not match - see + * {@link #indexOfType(Throwable, Class, int)} for the opposite.

              + * + *

              A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1. + * A negative start index is treated as zero. + * A start index greater than the number of throwables returns -1.

              + * + * @param throwable the throwable to inspect, may be null + * @param clazz the class to search for, subclasses do not match, null returns -1 + * @param fromIndex the (zero based) index of the starting position, + * negative treated as zero, larger than chain size returns -1 + * @return the index into the throwable chain, -1 if no match or null input + */ + public static int indexOfThrowable(final Throwable throwable, final Class clazz, final int fromIndex) { + return indexOf(throwable, clazz, fromIndex, false); + } + + //----------------------------------------------------------------------- + /** + *

              Returns the (zero based) index of the first Throwable + * that matches the specified class or subclass in the exception chain. + * Subclasses of the specified class do match - see + * {@link #indexOfThrowable(Throwable, Class)} for the opposite.

              + * + *

              A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1.

              + * + * @param throwable the throwable to inspect, may be null + * @param type the type to search for, subclasses match, null returns -1 + * @return the index into the throwable chain, -1 if no match or null input + * @since 2.1 + */ + public static int indexOfType(final Throwable throwable, final Class type) { + return indexOf(throwable, type, 0, true); + } + + /** + *

              Returns the (zero based) index of the first Throwable + * that matches the specified type in the exception chain from + * a specified index. + * Subclasses of the specified class do match - see + * {@link #indexOfThrowable(Throwable, Class)} for the opposite.

              + * + *

              A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1. + * A negative start index is treated as zero. + * A start index greater than the number of throwables returns -1.

              + * + * @param throwable the throwable to inspect, may be null + * @param type the type to search for, subclasses match, null returns -1 + * @param fromIndex the (zero based) index of the starting position, + * negative treated as zero, larger than chain size returns -1 + * @return the index into the throwable chain, -1 if no match or null input + * @since 2.1 + */ + public static int indexOfType(final Throwable throwable, final Class type, final int fromIndex) { + return indexOf(throwable, type, fromIndex, true); + } + + /** + *

              Worker method for the indexOfType methods.

              + * + * @param throwable the throwable to inspect, may be null + * @param type the type to search for, subclasses match, null returns -1 + * @param fromIndex the (zero based) index of the starting position, + * negative treated as zero, larger than chain size returns -1 + * @param subclass if true, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares + * using references + * @return index of the type within throwables nested within the specified throwable + */ + private static int indexOf(final Throwable throwable, final Class type, int fromIndex, final boolean subclass) { + if (throwable == null || type == null) { + return -1; + } + if (fromIndex < 0) { + fromIndex = 0; + } + final Throwable[] throwables = ExceptionUtils.getThrowables(throwable); + if (fromIndex >= throwables.length) { + return -1; + } + if (subclass) { + for (int i = fromIndex; i < throwables.length; i++) { + if (type.isAssignableFrom(throwables[i].getClass())) { + return i; + } + } + } else { + for (int i = fromIndex; i < throwables.length; i++) { + if (type.equals(throwables[i].getClass())) { + return i; + } + } + } + return -1; + } + + //----------------------------------------------------------------------- + /** + *

              Prints a compact stack trace for the root cause of a throwable + * to System.err.

              + * + *

              The compact stack trace starts with the root cause and prints + * stack frames up to the place where it was caught and wrapped. + * Then it prints the wrapped exception and continues with stack frames + * until the wrapper exception is caught and wrapped again, etc.

              + * + *

              The output of this method is consistent across JDK versions. + * Note that this is the opposite order to the JDK1.4 display.

              + * + *

              The method is equivalent to printStackTrace for throwables + * that don't have nested causes.

              + * + * @param throwable the throwable to output + * @since 2.0 + */ + public static void printRootCauseStackTrace(final Throwable throwable) { + printRootCauseStackTrace(throwable, System.err); + } + + /** + *

              Prints a compact stack trace for the root cause of a throwable.

              + * + *

              The compact stack trace starts with the root cause and prints + * stack frames up to the place where it was caught and wrapped. + * Then it prints the wrapped exception and continues with stack frames + * until the wrapper exception is caught and wrapped again, etc.

              + * + *

              The output of this method is consistent across JDK versions. + * Note that this is the opposite order to the JDK1.4 display.

              + * + *

              The method is equivalent to printStackTrace for throwables + * that don't have nested causes.

              + * + * @param throwable the throwable to output, may be null + * @param stream the stream to output to, may not be null + * @throws IllegalArgumentException if the stream is null + * @since 2.0 + */ + public static void printRootCauseStackTrace(final Throwable throwable, final PrintStream stream) { + if (throwable == null) { + return; + } + if (stream == null) { + throw new IllegalArgumentException("The PrintStream must not be null"); + } + final String trace[] = getRootCauseStackTrace(throwable); + for (final String element : trace) { + stream.println(element); + } + stream.flush(); + } + + /** + *

              Prints a compact stack trace for the root cause of a throwable.

              + * + *

              The compact stack trace starts with the root cause and prints + * stack frames up to the place where it was caught and wrapped. + * Then it prints the wrapped exception and continues with stack frames + * until the wrapper exception is caught and wrapped again, etc.

              + * + *

              The output of this method is consistent across JDK versions. + * Note that this is the opposite order to the JDK1.4 display.

              + * + *

              The method is equivalent to printStackTrace for throwables + * that don't have nested causes.

              + * + * @param throwable the throwable to output, may be null + * @param writer the writer to output to, may not be null + * @throws IllegalArgumentException if the writer is null + * @since 2.0 + */ + public static void printRootCauseStackTrace(final Throwable throwable, final PrintWriter writer) { + if (throwable == null) { + return; + } + if (writer == null) { + throw new IllegalArgumentException("The PrintWriter must not be null"); + } + final String trace[] = getRootCauseStackTrace(throwable); + for (final String element : trace) { + writer.println(element); + } + writer.flush(); + } + + //----------------------------------------------------------------------- + /** + *

              Creates a compact stack trace for the root cause of the supplied + * Throwable.

              + * + *

              The output of this method is consistent across JDK versions. + * It consists of the root exception followed by each of its wrapping + * exceptions separated by '[wrapped]'. Note that this is the opposite + * order to the JDK1.4 display.

              + * + * @param throwable the throwable to examine, may be null + * @return an array of stack trace frames, never null + * @since 2.0 + */ + public static String[] getRootCauseStackTrace(final Throwable throwable) { + if (throwable == null) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + final Throwable throwables[] = getThrowables(throwable); + final int count = throwables.length; + final List frames = new ArrayList(); + List nextTrace = getStackFrameList(throwables[count - 1]); + for (int i = count; --i >= 0;) { + final List trace = nextTrace; + if (i != 0) { + nextTrace = getStackFrameList(throwables[i - 1]); + removeCommonFrames(trace, nextTrace); + } + if (i == count - 1) { + frames.add(throwables[i].toString()); + } else { + frames.add(WRAPPED_MARKER + throwables[i].toString()); + } + for (int j = 0; j < trace.size(); j++) { + frames.add(trace.get(j)); + } + } + return frames.toArray(new String[frames.size()]); + } + + /** + *

              Removes common frames from the cause trace given the two stack traces.

              + * + * @param causeFrames stack trace of a cause throwable + * @param wrapperFrames stack trace of a wrapper throwable + * @throws IllegalArgumentException if either argument is null + * @since 2.0 + */ + public static void removeCommonFrames(final List causeFrames, final List wrapperFrames) { + if (causeFrames == null || wrapperFrames == null) { + throw new IllegalArgumentException("The List must not be null"); + } + int causeFrameIndex = causeFrames.size() - 1; + int wrapperFrameIndex = wrapperFrames.size() - 1; + while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) { + // Remove the frame from the cause trace if it is the same + // as in the wrapper trace + final String causeFrame = causeFrames.get(causeFrameIndex); + final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex); + if (causeFrame.equals(wrapperFrame)) { + causeFrames.remove(causeFrameIndex); + } + causeFrameIndex--; + wrapperFrameIndex--; + } + } + + //----------------------------------------------------------------------- + /** + *

              Gets the stack trace from a Throwable as a String.

              + * + *

              The result of this method vary by JDK version as this method + * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. + * On JDK1.3 and earlier, the cause exception will not be shown + * unless the specified throwable alters printStackTrace.

              + * + * @param throwable the Throwable to be examined + * @return the stack trace as generated by the exception's + * printStackTrace(PrintWriter) method + */ + public static String getStackTrace(final Throwable throwable) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, true); + throwable.printStackTrace(pw); + return sw.getBuffer().toString(); + } + + /** + *

              Captures the stack trace associated with the specified + * Throwable object, decomposing it into a list of + * stack frames.

              + * + *

              The result of this method vary by JDK version as this method + * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. + * On JDK1.3 and earlier, the cause exception will not be shown + * unless the specified throwable alters printStackTrace.

              + * + * @param throwable the Throwable to examine, may be null + * @return an array of strings describing each stack frame, never null + */ + public static String[] getStackFrames(final Throwable throwable) { + if (throwable == null) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + return getStackFrames(getStackTrace(throwable)); + } + + //----------------------------------------------------------------------- + /** + *

              Returns an array where each element is a line from the argument.

              + * + *

              The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.

              + * + * @param stackTrace a stack trace String + * @return an array where each element is a line from the argument + */ + static String[] getStackFrames(final String stackTrace) { + final String linebreak = SystemUtils.LINE_SEPARATOR; + final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak); + final List list = new ArrayList(); + while (frames.hasMoreTokens()) { + list.add(frames.nextToken()); + } + return list.toArray(new String[list.size()]); + } + + /** + *

              Produces a List of stack frames - the message + * is not included. Only the trace of the specified exception is + * returned, any caused by trace is stripped.

              + * + *

              This works in most cases - it will only fail if the exception + * message contains a line that starts with: + * "   at".

              + * + * @param t is any throwable + * @return List of stack frames + */ + static List getStackFrameList(final Throwable t) { + final String stackTrace = getStackTrace(t); + final String linebreak = SystemUtils.LINE_SEPARATOR; + final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak); + final List list = new ArrayList(); + boolean traceStarted = false; + while (frames.hasMoreTokens()) { + final String token = frames.nextToken(); + // Determine if the line starts with at + final int at = token.indexOf("at"); + if (at != -1 && token.substring(0, at).trim().isEmpty()) { + traceStarted = true; + list.add(token); + } else if (traceStarted) { + break; + } + } + return list; + } + + //----------------------------------------------------------------------- + /** + * Gets a short message summarising the exception. + *

              + * The message returned is of the form + * {ClassNameWithoutPackage}: {ThrowableMessage} + * + * @param th the throwable to get a message for, null returns empty string + * @return the message, non-null + * @since Commons Lang 2.2 + */ + public static String getMessage(final Throwable th) { + if (th == null) { + return ""; + } + final String clsName = ClassUtils.getShortClassName(th, null); + final String msg = th.getMessage(); + return clsName + ": " + StringUtils.defaultString(msg); + } + + //----------------------------------------------------------------------- + /** + * Gets a short message summarising the root cause exception. + *

              + * The message returned is of the form + * {ClassNameWithoutPackage}: {ThrowableMessage} + * + * @param th the throwable to get a message for, null returns empty string + * @return the message, non-null + * @since Commons Lang 2.2 + */ + public static String getRootCauseMessage(final Throwable th) { + Throwable root = ExceptionUtils.getRootCause(th); + root = root == null ? th : root; + return getMessage(root); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/package-info.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/package-info.java new file mode 100644 index 000000000..5d31a4291 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/exception/package-info.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *

              Provides functionality for Exceptions.

              + *

              Contains the concept of an exception with context i.e. such an exception will contain a map with keys and values. + * This provides an easy way to pass valuable state information at exception time in useful form to a calling process.

              + *

              Lastly, {@link com.fr.third.org.apache.commons.lang3.exception.ExceptionUtils} also contains Throwable manipulation + * and examination routines.

              + * + * @since 1.0 + * @version $Id: package-info.java 1558546 2014-01-15 19:38:15Z britter $ + */ +package com.fr.third.org.apache.commons.lang3.exception; diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/Fraction.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/Fraction.java new file mode 100644 index 000000000..4aa4ff151 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/Fraction.java @@ -0,0 +1,942 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.math; + +import java.math.BigInteger; + +/** + *

              Fraction is a Number implementation that + * stores fractions accurately.

              + * + *

              This class is immutable, and interoperable with most methods that accept + * a Number.

              + * + *

              Note that this class is intended for common use cases, it is int + * based and thus suffers from various overflow issues. For a BigInteger based + * equivalent, please see the Commons Math BigFraction class.

              + * + * @since 2.0 + * @version $Id: Fraction.java 1606086 2014-06-27 13:09:03Z ggregory $ + */ +public final class Fraction extends Number implements Comparable { + + /** + * Required for serialization support. Lang version 2.0. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 65382027393090L; + + /** + * Fraction representation of 0. + */ + public static final Fraction ZERO = new Fraction(0, 1); + /** + * Fraction representation of 1. + */ + public static final Fraction ONE = new Fraction(1, 1); + /** + * Fraction representation of 1/2. + */ + public static final Fraction ONE_HALF = new Fraction(1, 2); + /** + * Fraction representation of 1/3. + */ + public static final Fraction ONE_THIRD = new Fraction(1, 3); + /** + * Fraction representation of 2/3. + */ + public static final Fraction TWO_THIRDS = new Fraction(2, 3); + /** + * Fraction representation of 1/4. + */ + public static final Fraction ONE_QUARTER = new Fraction(1, 4); + /** + * Fraction representation of 2/4. + */ + public static final Fraction TWO_QUARTERS = new Fraction(2, 4); + /** + * Fraction representation of 3/4. + */ + public static final Fraction THREE_QUARTERS = new Fraction(3, 4); + /** + * Fraction representation of 1/5. + */ + public static final Fraction ONE_FIFTH = new Fraction(1, 5); + /** + * Fraction representation of 2/5. + */ + public static final Fraction TWO_FIFTHS = new Fraction(2, 5); + /** + * Fraction representation of 3/5. + */ + public static final Fraction THREE_FIFTHS = new Fraction(3, 5); + /** + * Fraction representation of 4/5. + */ + public static final Fraction FOUR_FIFTHS = new Fraction(4, 5); + + + /** + * The numerator number part of the fraction (the three in three sevenths). + */ + private final int numerator; + /** + * The denominator number part of the fraction (the seven in three sevenths). + */ + private final int denominator; + + /** + * Cached output hashCode (class is immutable). + */ + private transient int hashCode = 0; + /** + * Cached output toString (class is immutable). + */ + private transient String toString = null; + /** + * Cached output toProperString (class is immutable). + */ + private transient String toProperString = null; + + /** + *

              Constructs a Fraction instance with the 2 parts + * of a fraction Y/Z.

              + * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + */ + private Fraction(final int numerator, final int denominator) { + super(); + this.numerator = numerator; + this.denominator = denominator; + } + + /** + *

              Creates a Fraction instance with the 2 parts + * of a fraction Y/Z.

              + * + *

              Any negative signs are resolved to be on the numerator.

              + * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + * @return a new fraction instance + * @throws ArithmeticException if the denominator is zero + * or the denominator is {@code negative} and the numerator is {@code Integer#MIN_VALUE} + */ + public static Fraction getFraction(int numerator, int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + if (denominator < 0) { + if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate"); + } + numerator = -numerator; + denominator = -denominator; + } + return new Fraction(numerator, denominator); + } + + /** + *

              Creates a Fraction instance with the 3 parts + * of a fraction X Y/Z.

              + * + *

              The negative sign must be passed in on the whole number part.

              + * + * @param whole the whole number, for example the one in 'one and three sevenths' + * @param numerator the numerator, for example the three in 'one and three sevenths' + * @param denominator the denominator, for example the seven in 'one and three sevenths' + * @return a new fraction instance + * @throws ArithmeticException if the denominator is zero + * @throws ArithmeticException if the denominator is negative + * @throws ArithmeticException if the numerator is negative + * @throws ArithmeticException if the resulting numerator exceeds + * Integer.MAX_VALUE + */ + public static Fraction getFraction(final int whole, final int numerator, final int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + if (denominator < 0) { + throw new ArithmeticException("The denominator must not be negative"); + } + if (numerator < 0) { + throw new ArithmeticException("The numerator must not be negative"); + } + long numeratorValue; + if (whole < 0) { + numeratorValue = whole * (long) denominator - numerator; + } else { + numeratorValue = whole * (long) denominator + numerator; + } + if (numeratorValue < Integer.MIN_VALUE || numeratorValue > Integer.MAX_VALUE) { + throw new ArithmeticException("Numerator too large to represent as an Integer."); + } + return new Fraction((int) numeratorValue, denominator); + } + + /** + *

              Creates a reduced Fraction instance with the 2 parts + * of a fraction Y/Z.

              + * + *

              For example, if the input parameters represent 2/4, then the created + * fraction will be 1/2.

              + * + *

              Any negative signs are resolved to be on the numerator.

              + * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + * @return a new fraction instance, with the numerator and denominator reduced + * @throws ArithmeticException if the denominator is zero + */ + public static Fraction getReducedFraction(int numerator, int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + if (numerator == 0) { + return ZERO; // normalize zero. + } + // allow 2^k/-2^31 as a valid fraction (where k>0) + if (denominator == Integer.MIN_VALUE && (numerator & 1) == 0) { + numerator /= 2; + denominator /= 2; + } + if (denominator < 0) { + if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate"); + } + numerator = -numerator; + denominator = -denominator; + } + // simplify fraction. + final int gcd = greatestCommonDivisor(numerator, denominator); + numerator /= gcd; + denominator /= gcd; + return new Fraction(numerator, denominator); + } + + /** + *

              Creates a Fraction instance from a double value.

              + * + *

              This method uses the + * continued fraction algorithm, computing a maximum of + * 25 convergents and bounding the denominator by 10,000.

              + * + * @param value the double value to convert + * @return a new fraction instance that is close to the value + * @throws ArithmeticException if |value| > Integer.MAX_VALUE + * or value = NaN + * @throws ArithmeticException if the calculated denominator is zero + * @throws ArithmeticException if the the algorithm does not converge + */ + public static Fraction getFraction(double value) { + final int sign = value < 0 ? -1 : 1; + value = Math.abs(value); + if (value > Integer.MAX_VALUE || Double.isNaN(value)) { + throw new ArithmeticException("The value must not be greater than Integer.MAX_VALUE or NaN"); + } + final int wholeNumber = (int) value; + value -= wholeNumber; + + int numer0 = 0; // the pre-previous + int denom0 = 1; // the pre-previous + int numer1 = 1; // the previous + int denom1 = 0; // the previous + int numer2 = 0; // the current, setup in calculation + int denom2 = 0; // the current, setup in calculation + int a1 = (int) value; + int a2 = 0; + double x1 = 1; + double x2 = 0; + double y1 = value - a1; + double y2 = 0; + double delta1, delta2 = Double.MAX_VALUE; + double fraction; + int i = 1; + // System.out.println("---"); + do { + delta1 = delta2; + a2 = (int) (x1 / y1); + x2 = y1; + y2 = x1 - a2 * y1; + numer2 = a1 * numer1 + numer0; + denom2 = a1 * denom1 + denom0; + fraction = (double) numer2 / (double) denom2; + delta2 = Math.abs(value - fraction); + // System.out.println(numer2 + " " + denom2 + " " + fraction + " " + delta2 + " " + y1); + a1 = a2; + x1 = x2; + y1 = y2; + numer0 = numer1; + denom0 = denom1; + numer1 = numer2; + denom1 = denom2; + i++; + // System.out.println(">>" + delta1 +" "+ delta2+" "+(delta1 > delta2)+" "+i+" "+denom2); + } while (delta1 > delta2 && denom2 <= 10000 && denom2 > 0 && i < 25); + if (i == 25) { + throw new ArithmeticException("Unable to convert double to fraction"); + } + return getReducedFraction((numer0 + wholeNumber * denom0) * sign, denom0); + } + + /** + *

              Creates a Fraction from a String.

              + * + *

              The formats accepted are:

              + * + *
                + *
              1. double String containing a dot
              2. + *
              3. 'X Y/Z'
              4. + *
              5. 'Y/Z'
              6. + *
              7. 'X' (a simple whole number)
              8. + *
              + *

              and a .

              + * + * @param str the string to parse, must not be null + * @return the new Fraction instance + * @throws IllegalArgumentException if the string is null + * @throws NumberFormatException if the number format is invalid + */ + public static Fraction getFraction(String str) { + if (str == null) { + throw new IllegalArgumentException("The string must not be null"); + } + // parse double format + int pos = str.indexOf('.'); + if (pos >= 0) { + return getFraction(Double.parseDouble(str)); + } + + // parse X Y/Z format + pos = str.indexOf(' '); + if (pos > 0) { + final int whole = Integer.parseInt(str.substring(0, pos)); + str = str.substring(pos + 1); + pos = str.indexOf('/'); + if (pos < 0) { + throw new NumberFormatException("The fraction could not be parsed as the format X Y/Z"); + } + final int numer = Integer.parseInt(str.substring(0, pos)); + final int denom = Integer.parseInt(str.substring(pos + 1)); + return getFraction(whole, numer, denom); + } + + // parse Y/Z format + pos = str.indexOf('/'); + if (pos < 0) { + // simple whole number + return getFraction(Integer.parseInt(str), 1); + } + final int numer = Integer.parseInt(str.substring(0, pos)); + final int denom = Integer.parseInt(str.substring(pos + 1)); + return getFraction(numer, denom); + } + + // Accessors + //------------------------------------------------------------------- + + /** + *

              Gets the numerator part of the fraction.

              + * + *

              This method may return a value greater than the denominator, an + * improper fraction, such as the seven in 7/4.

              + * + * @return the numerator fraction part + */ + public int getNumerator() { + return numerator; + } + + /** + *

              Gets the denominator part of the fraction.

              + * + * @return the denominator fraction part + */ + public int getDenominator() { + return denominator; + } + + /** + *

              Gets the proper numerator, always positive.

              + * + *

              An improper fraction 7/4 can be resolved into a proper one, 1 3/4. + * This method returns the 3 from the proper fraction.

              + * + *

              If the fraction is negative such as -7/4, it can be resolved into + * -1 3/4, so this method returns the positive proper numerator, 3.

              + * + * @return the numerator fraction part of a proper fraction, always positive + */ + public int getProperNumerator() { + return Math.abs(numerator % denominator); + } + + /** + *

              Gets the proper whole part of the fraction.

              + * + *

              An improper fraction 7/4 can be resolved into a proper one, 1 3/4. + * This method returns the 1 from the proper fraction.

              + * + *

              If the fraction is negative such as -7/4, it can be resolved into + * -1 3/4, so this method returns the positive whole part -1.

              + * + * @return the whole fraction part of a proper fraction, that includes the sign + */ + public int getProperWhole() { + return numerator / denominator; + } + + // Number methods + //------------------------------------------------------------------- + + /** + *

              Gets the fraction as an int. This returns the whole number + * part of the fraction.

              + * + * @return the whole number fraction part + */ + @Override + public int intValue() { + return numerator / denominator; + } + + /** + *

              Gets the fraction as a long. This returns the whole number + * part of the fraction.

              + * + * @return the whole number fraction part + */ + @Override + public long longValue() { + return (long) numerator / denominator; + } + + /** + *

              Gets the fraction as a float. This calculates the fraction + * as the numerator divided by denominator.

              + * + * @return the fraction as a float + */ + @Override + public float floatValue() { + return (float) numerator / (float) denominator; + } + + /** + *

              Gets the fraction as a double. This calculates the fraction + * as the numerator divided by denominator.

              + * + * @return the fraction as a double + */ + @Override + public double doubleValue() { + return (double) numerator / (double) denominator; + } + + // Calculations + //------------------------------------------------------------------- + + /** + *

              Reduce the fraction to the smallest values for the numerator and + * denominator, returning the result.

              + * + *

              For example, if this fraction represents 2/4, then the result + * will be 1/2.

              + * + * @return a new reduced fraction instance, or this if no simplification possible + */ + public Fraction reduce() { + if (numerator == 0) { + return equals(ZERO) ? this : ZERO; + } + final int gcd = greatestCommonDivisor(Math.abs(numerator), denominator); + if (gcd == 1) { + return this; + } + return Fraction.getFraction(numerator / gcd, denominator / gcd); + } + + /** + *

              Gets a fraction that is the inverse (1/fraction) of this one.

              + * + *

              The returned fraction is not reduced.

              + * + * @return a new fraction instance with the numerator and denominator + * inverted. + * @throws ArithmeticException if the fraction represents zero. + */ + public Fraction invert() { + if (numerator == 0) { + throw new ArithmeticException("Unable to invert zero."); + } + if (numerator==Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate numerator"); + } + if (numerator<0) { + return new Fraction(-denominator, -numerator); + } + return new Fraction(denominator, numerator); + } + + /** + *

              Gets a fraction that is the negative (-fraction) of this one.

              + * + *

              The returned fraction is not reduced.

              + * + * @return a new fraction instance with the opposite signed numerator + */ + public Fraction negate() { + // the positive range is one smaller than the negative range of an int. + if (numerator==Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: too large to negate"); + } + return new Fraction(-numerator, denominator); + } + + /** + *

              Gets a fraction that is the positive equivalent of this one.

              + *

              More precisely: (fraction >= 0 ? this : -fraction)

              + * + *

              The returned fraction is not reduced.

              + * + * @return this if it is positive, or a new positive fraction + * instance with the opposite signed numerator + */ + public Fraction abs() { + if (numerator >= 0) { + return this; + } + return negate(); + } + + /** + *

              Gets a fraction that is raised to the passed in power.

              + * + *

              The returned fraction is in reduced form.

              + * + * @param power the power to raise the fraction to + * @return this if the power is one, ONE if the power + * is zero (even if the fraction equals ZERO) or a new fraction instance + * raised to the appropriate power + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * Integer.MAX_VALUE + */ + public Fraction pow(final int power) { + if (power == 1) { + return this; + } else if (power == 0) { + return ONE; + } else if (power < 0) { + if (power == Integer.MIN_VALUE) { // MIN_VALUE can't be negated. + return this.invert().pow(2).pow(-(power / 2)); + } + return this.invert().pow(-power); + } else { + final Fraction f = this.multiplyBy(this); + if (power % 2 == 0) { // if even... + return f.pow(power / 2); + } + return f.pow(power / 2).multiplyBy(this); + } + } + + /** + *

              Gets the greatest common divisor of the absolute value of + * two numbers, using the "binary gcd" method which avoids + * division and modulo operations. See Knuth 4.5.2 algorithm B. + * This algorithm is due to Josef Stein (1961).

              + * + * @param u a non-zero number + * @param v a non-zero number + * @return the greatest common divisor, never zero + */ + private static int greatestCommonDivisor(int u, int v) { + // From Commons Math: + if (u == 0 || v == 0) { + if (u == Integer.MIN_VALUE || v == Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: gcd is 2^31"); + } + return Math.abs(u) + Math.abs(v); + } + // if either operand is abs 1, return 1: + if (Math.abs(u) == 1 || Math.abs(v) == 1) { + return 1; + } + // keep u and v negative, as negative integers range down to + // -2^31, while positive numbers can only be as large as 2^31-1 + // (i.e. we can't necessarily negate a negative number without + // overflow) + if (u > 0) { + u = -u; + } // make u negative + if (v > 0) { + v = -v; + } // make v negative + // B1. [Find power of 2] + int k = 0; + while ((u & 1) == 0 && (v & 1) == 0 && k < 31) { // while u and v are both even... + u /= 2; + v /= 2; + k++; // cast out twos. + } + if (k == 31) { + throw new ArithmeticException("overflow: gcd is 2^31"); + } + // B2. Initialize: u and v have been divided by 2^k and at least + // one is odd. + int t = (u & 1) == 1 ? v : -(u / 2)/* B3 */; + // t negative: u was odd, v may be even (t replaces v) + // t positive: u was even, v is odd (t replaces u) + do { + /* assert u<0 && v<0; */ + // B4/B3: cast out twos from t. + while ((t & 1) == 0) { // while t is even.. + t /= 2; // cast out twos + } + // B5 [reset max(u,v)] + if (t > 0) { + u = -t; + } else { + v = t; + } + // B6/B3. at this point both u and v should be odd. + t = (v - u) / 2; + // |u| larger: t positive (replace u) + // |v| larger: t negative (replace v) + } while (t != 0); + return -u * (1 << k); // gcd is u*2^k + } + + // Arithmetic + //------------------------------------------------------------------- + + /** + * Multiply two integers, checking for overflow. + * + * @param x a factor + * @param y a factor + * @return the product x*y + * @throws ArithmeticException if the result can not be represented as + * an int + */ + private static int mulAndCheck(final int x, final int y) { + final long m = (long) x * (long) y; + if (m < Integer.MIN_VALUE || m > Integer.MAX_VALUE) { + throw new ArithmeticException("overflow: mul"); + } + return (int) m; + } + + /** + * Multiply two non-negative integers, checking for overflow. + * + * @param x a non-negative factor + * @param y a non-negative factor + * @return the product x*y + * @throws ArithmeticException if the result can not be represented as + * an int + */ + private static int mulPosAndCheck(final int x, final int y) { + /* assert x>=0 && y>=0; */ + final long m = (long) x * (long) y; + if (m > Integer.MAX_VALUE) { + throw new ArithmeticException("overflow: mulPos"); + } + return (int) m; + } + + /** + * Add two integers, checking for overflow. + * + * @param x an addend + * @param y an addend + * @return the sum x+y + * @throws ArithmeticException if the result can not be represented as + * an int + */ + private static int addAndCheck(final int x, final int y) { + final long s = (long) x + (long) y; + if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) { + throw new ArithmeticException("overflow: add"); + } + return (int) s; + } + + /** + * Subtract two integers, checking for overflow. + * + * @param x the minuend + * @param y the subtrahend + * @return the difference x-y + * @throws ArithmeticException if the result can not be represented as + * an int + */ + private static int subAndCheck(final int x, final int y) { + final long s = (long) x - (long) y; + if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) { + throw new ArithmeticException("overflow: add"); + } + return (int) s; + } + + /** + *

              Adds the value of this fraction to another, returning the result in reduced form. + * The algorithm follows Knuth, 4.5.1.

              + * + * @param fraction the fraction to add, must not be null + * @return a Fraction instance with the resulting values + * @throws IllegalArgumentException if the fraction is null + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * Integer.MAX_VALUE + */ + public Fraction add(final Fraction fraction) { + return addSub(fraction, true /* add */); + } + + /** + *

              Subtracts the value of another fraction from the value of this one, + * returning the result in reduced form.

              + * + * @param fraction the fraction to subtract, must not be null + * @return a Fraction instance with the resulting values + * @throws IllegalArgumentException if the fraction is null + * @throws ArithmeticException if the resulting numerator or denominator + * cannot be represented in an int. + */ + public Fraction subtract(final Fraction fraction) { + return addSub(fraction, false /* subtract */); + } + + /** + * Implement add and subtract using algorithm described in Knuth 4.5.1. + * + * @param fraction the fraction to subtract, must not be null + * @param isAdd true to add, false to subtract + * @return a Fraction instance with the resulting values + * @throws IllegalArgumentException if the fraction is null + * @throws ArithmeticException if the resulting numerator or denominator + * cannot be represented in an int. + */ + private Fraction addSub(final Fraction fraction, final boolean isAdd) { + if (fraction == null) { + throw new IllegalArgumentException("The fraction must not be null"); + } + // zero is identity for addition. + if (numerator == 0) { + return isAdd ? fraction : fraction.negate(); + } + if (fraction.numerator == 0) { + return this; + } + // if denominators are randomly distributed, d1 will be 1 about 61% + // of the time. + final int d1 = greatestCommonDivisor(denominator, fraction.denominator); + if (d1 == 1) { + // result is ( (u*v' +/- u'v) / u'v') + final int uvp = mulAndCheck(numerator, fraction.denominator); + final int upv = mulAndCheck(fraction.numerator, denominator); + return new Fraction(isAdd ? addAndCheck(uvp, upv) : subAndCheck(uvp, upv), mulPosAndCheck(denominator, + fraction.denominator)); + } + // the quantity 't' requires 65 bits of precision; see knuth 4.5.1 + // exercise 7. we're going to use a BigInteger. + // t = u(v'/d1) +/- v(u'/d1) + final BigInteger uvp = BigInteger.valueOf(numerator).multiply(BigInteger.valueOf(fraction.denominator / d1)); + final BigInteger upv = BigInteger.valueOf(fraction.numerator).multiply(BigInteger.valueOf(denominator / d1)); + final BigInteger t = isAdd ? uvp.add(upv) : uvp.subtract(upv); + // but d2 doesn't need extra precision because + // d2 = gcd(t,d1) = gcd(t mod d1, d1) + final int tmodd1 = t.mod(BigInteger.valueOf(d1)).intValue(); + final int d2 = tmodd1 == 0 ? d1 : greatestCommonDivisor(tmodd1, d1); + + // result is (t/d2) / (u'/d1)(v'/d2) + final BigInteger w = t.divide(BigInteger.valueOf(d2)); + if (w.bitLength() > 31) { + throw new ArithmeticException("overflow: numerator too large after multiply"); + } + return new Fraction(w.intValue(), mulPosAndCheck(denominator / d1, fraction.denominator / d2)); + } + + /** + *

              Multiplies the value of this fraction by another, returning the + * result in reduced form.

              + * + * @param fraction the fraction to multiply by, must not be null + * @return a Fraction instance with the resulting values + * @throws IllegalArgumentException if the fraction is null + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * Integer.MAX_VALUE + */ + public Fraction multiplyBy(final Fraction fraction) { + if (fraction == null) { + throw new IllegalArgumentException("The fraction must not be null"); + } + if (numerator == 0 || fraction.numerator == 0) { + return ZERO; + } + // knuth 4.5.1 + // make sure we don't overflow unless the result *must* overflow. + final int d1 = greatestCommonDivisor(numerator, fraction.denominator); + final int d2 = greatestCommonDivisor(fraction.numerator, denominator); + return getReducedFraction(mulAndCheck(numerator / d1, fraction.numerator / d2), + mulPosAndCheck(denominator / d2, fraction.denominator / d1)); + } + + /** + *

              Divide the value of this fraction by another.

              + * + * @param fraction the fraction to divide by, must not be null + * @return a Fraction instance with the resulting values + * @throws IllegalArgumentException if the fraction is null + * @throws ArithmeticException if the fraction to divide by is zero + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * Integer.MAX_VALUE + */ + public Fraction divideBy(final Fraction fraction) { + if (fraction == null) { + throw new IllegalArgumentException("The fraction must not be null"); + } + if (fraction.numerator == 0) { + throw new ArithmeticException("The fraction to divide by must not be zero"); + } + return multiplyBy(fraction.invert()); + } + + // Basics + //------------------------------------------------------------------- + + /** + *

              Compares this fraction to another object to test if they are equal.

              . + * + *

              To be equal, both values must be equal. Thus 2/4 is not equal to 1/2.

              + * + * @param obj the reference object with which to compare + * @return true if this object is equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Fraction == false) { + return false; + } + final Fraction other = (Fraction) obj; + return getNumerator() == other.getNumerator() && getDenominator() == other.getDenominator(); + } + + /** + *

              Gets a hashCode for the fraction.

              + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (hashCode == 0) { + // hashcode update should be atomic. + hashCode = 37 * (37 * 17 + getNumerator()) + getDenominator(); + } + return hashCode; + } + + /** + *

              Compares this object to another based on size.

              + * + *

              Note: this class has a natural ordering that is inconsistent + * with equals, because, for example, equals treats 1/2 and 2/4 as + * different, whereas compareTo treats them as equal. + * + * @param other the object to compare to + * @return -1 if this is less, 0 if equal, +1 if greater + * @throws ClassCastException if the object is not a Fraction + * @throws NullPointerException if the object is null + */ + @Override + public int compareTo(final Fraction other) { + if (this == other) { + return 0; + } + if (numerator == other.numerator && denominator == other.denominator) { + return 0; + } + + // otherwise see which is less + final long first = (long) numerator * (long) other.denominator; + final long second = (long) other.numerator * (long) denominator; + if (first == second) { + return 0; + } else if (first < second) { + return -1; + } else { + return 1; + } + } + + /** + *

              Gets the fraction as a String.

              + * + *

              The format used is 'numerator/denominator' always. + * + * @return a String form of the fraction + */ + @Override + public String toString() { + if (toString == null) { + toString = new StringBuilder(32).append(getNumerator()).append('/').append(getDenominator()).toString(); + } + return toString; + } + + /** + *

              Gets the fraction as a proper String in the format X Y/Z.

              + * + *

              The format used in 'wholeNumber numerator/denominator'. + * If the whole number is zero it will be omitted. If the numerator is zero, + * only the whole number is returned.

              + * + * @return a String form of the fraction + */ + public String toProperString() { + if (toProperString == null) { + if (numerator == 0) { + toProperString = "0"; + } else if (numerator == denominator) { + toProperString = "1"; + } else if (numerator == -1 * denominator) { + toProperString = "-1"; + } else if ((numerator > 0 ? -numerator : numerator) < -denominator) { + // note that we do the magnitude comparison test above with + // NEGATIVE (not positive) numbers, since negative numbers + // have a larger range. otherwise numerator==Integer.MIN_VALUE + // is handled incorrectly. + final int properNumerator = getProperNumerator(); + if (properNumerator == 0) { + toProperString = Integer.toString(getProperWhole()); + } else { + toProperString = new StringBuilder(32).append(getProperWhole()).append(' ').append(properNumerator) + .append('/').append(getDenominator()).toString(); + } + } else { + toProperString = new StringBuilder(32).append(getNumerator()).append('/').append(getDenominator()) + .toString(); + } + } + return toProperString; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/IEEE754rUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/IEEE754rUtils.java new file mode 100644 index 000000000..a6a7ce524 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/IEEE754rUtils.java @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.math; + +import com.fr.third.org.apache.commons.lang3.Validate; + +/** + *

              Provides IEEE-754r variants of NumberUtils methods.

              + * + *

              See: http://en.wikipedia.org/wiki/IEEE_754r

              + * + * @since 2.4 + * @version $Id: IEEE754rUtils.java 1593679 2014-05-10 07:22:22Z djones $ + */ +public class IEEE754rUtils { + + /** + *

              Returns the minimum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from min(double[]) to min(double...) + */ + public static double min(final double... array) { + // Validates input + if (array == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + + // Finds and returns min + double min = array[0]; + for (int i = 1; i < array.length; i++) { + min = min(array[i], min); + } + + return min; + } + + /** + *

              Returns the minimum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from min(float[]) to min(float...) + */ + public static float min(final float... array) { + // Validates input + if (array == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + // Finds and returns min + float min = array[0]; + for (int i = 1; i < array.length; i++) { + min = min(array[i], min); + } + + return min; + } + + /** + *

              Gets the minimum of three double values.

              + * + *

              NaN is only returned if all numbers are NaN as per IEEE-754r.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static double min(final double a, final double b, final double c) { + return min(min(a, b), c); + } + + /** + *

              Gets the minimum of two double values.

              + * + *

              NaN is only returned if all numbers are NaN as per IEEE-754r.

              + * + * @param a value 1 + * @param b value 2 + * @return the smallest of the values + */ + public static double min(final double a, final double b) { + if(Double.isNaN(a)) { + return b; + } else + if(Double.isNaN(b)) { + return a; + } else { + return Math.min(a, b); + } + } + + /** + *

              Gets the minimum of three float values.

              + * + *

              NaN is only returned if all numbers are NaN as per IEEE-754r.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static float min(final float a, final float b, final float c) { + return min(min(a, b), c); + } + + /** + *

              Gets the minimum of two float values.

              + * + *

              NaN is only returned if all numbers are NaN as per IEEE-754r.

              + * + * @param a value 1 + * @param b value 2 + * @return the smallest of the values + */ + public static float min(final float a, final float b) { + if(Float.isNaN(a)) { + return b; + } else + if(Float.isNaN(b)) { + return a; + } else { + return Math.min(a, b); + } + } + + /** + *

              Returns the maximum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from max(double[]) to max(double...) + */ + public static double max(final double... array) { + // Validates input + if (array== null) { + throw new IllegalArgumentException("The Array must not be null"); + } + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + // Finds and returns max + double max = array[0]; + for (int j = 1; j < array.length; j++) { + max = max(array[j], max); + } + + return max; + } + + /** + *

              Returns the maximum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from max(float[]) to max(float...) + */ + public static float max(final float... array) { + // Validates input + if (array == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + // Finds and returns max + float max = array[0]; + for (int j = 1; j < array.length; j++) { + max = max(array[j], max); + } + + return max; + } + + /** + *

              Gets the maximum of three double values.

              + * + *

              NaN is only returned if all numbers are NaN as per IEEE-754r.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static double max(final double a, final double b, final double c) { + return max(max(a, b), c); + } + + /** + *

              Gets the maximum of two double values.

              + * + *

              NaN is only returned if all numbers are NaN as per IEEE-754r.

              + * + * @param a value 1 + * @param b value 2 + * @return the largest of the values + */ + public static double max(final double a, final double b) { + if(Double.isNaN(a)) { + return b; + } else + if(Double.isNaN(b)) { + return a; + } else { + return Math.max(a, b); + } + } + + /** + *

              Gets the maximum of three float values.

              + * + *

              NaN is only returned if all numbers are NaN as per IEEE-754r.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static float max(final float a, final float b, final float c) { + return max(max(a, b), c); + } + + /** + *

              Gets the maximum of two float values.

              + * + *

              NaN is only returned if all numbers are NaN as per IEEE-754r.

              + * + * @param a value 1 + * @param b value 2 + * @return the largest of the values + */ + public static float max(final float a, final float b) { + if(Float.isNaN(a)) { + return b; + } else + if(Float.isNaN(b)) { + return a; + } else { + return Math.max(a, b); + } + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/NumberUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/NumberUtils.java new file mode 100644 index 000000000..e79704c66 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/NumberUtils.java @@ -0,0 +1,1602 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.math; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; + +import com.fr.third.org.apache.commons.lang3.StringUtils; +import com.fr.third.org.apache.commons.lang3.Validate; + +/** + *

              Provides extra functionality for Java Number classes.

              + * + * @since 2.0 + * @version $Id: NumberUtils.java 1663129 2015-03-01 16:48:22Z britter $ + */ +public class NumberUtils { + + /** Reusable Long constant for zero. */ + public static final Long LONG_ZERO = Long.valueOf(0L); + /** Reusable Long constant for one. */ + public static final Long LONG_ONE = Long.valueOf(1L); + /** Reusable Long constant for minus one. */ + public static final Long LONG_MINUS_ONE = Long.valueOf(-1L); + /** Reusable Integer constant for zero. */ + public static final Integer INTEGER_ZERO = Integer.valueOf(0); + /** Reusable Integer constant for one. */ + public static final Integer INTEGER_ONE = Integer.valueOf(1); + /** Reusable Integer constant for minus one. */ + public static final Integer INTEGER_MINUS_ONE = Integer.valueOf(-1); + /** Reusable Short constant for zero. */ + public static final Short SHORT_ZERO = Short.valueOf((short) 0); + /** Reusable Short constant for one. */ + public static final Short SHORT_ONE = Short.valueOf((short) 1); + /** Reusable Short constant for minus one. */ + public static final Short SHORT_MINUS_ONE = Short.valueOf((short) -1); + /** Reusable Byte constant for zero. */ + public static final Byte BYTE_ZERO = Byte.valueOf((byte) 0); + /** Reusable Byte constant for one. */ + public static final Byte BYTE_ONE = Byte.valueOf((byte) 1); + /** Reusable Byte constant for minus one. */ + public static final Byte BYTE_MINUS_ONE = Byte.valueOf((byte) -1); + /** Reusable Double constant for zero. */ + public static final Double DOUBLE_ZERO = Double.valueOf(0.0d); + /** Reusable Double constant for one. */ + public static final Double DOUBLE_ONE = Double.valueOf(1.0d); + /** Reusable Double constant for minus one. */ + public static final Double DOUBLE_MINUS_ONE = Double.valueOf(-1.0d); + /** Reusable Float constant for zero. */ + public static final Float FLOAT_ZERO = Float.valueOf(0.0f); + /** Reusable Float constant for one. */ + public static final Float FLOAT_ONE = Float.valueOf(1.0f); + /** Reusable Float constant for minus one. */ + public static final Float FLOAT_MINUS_ONE = Float.valueOf(-1.0f); + + /** + *

              NumberUtils instances should NOT be constructed in standard programming. + * Instead, the class should be used as NumberUtils.toInt("6");.

              + * + *

              This constructor is public to permit tools that require a JavaBean instance + * to operate.

              + */ + public NumberUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + *

              Convert a String to an int, returning + * zero if the conversion fails.

              + * + *

              If the string is null, zero is returned.

              + * + *
              +     *   NumberUtils.toInt(null) = 0
              +     *   NumberUtils.toInt("")   = 0
              +     *   NumberUtils.toInt("1")  = 1
              +     * 
              + * + * @param str the string to convert, may be null + * @return the int represented by the string, or zero if + * conversion fails + * @since 2.1 + */ + public static int toInt(final String str) { + return toInt(str, 0); + } + + /** + *

              Convert a String to an int, returning a + * default value if the conversion fails.

              + * + *

              If the string is null, the default value is returned.

              + * + *
              +     *   NumberUtils.toInt(null, 1) = 1
              +     *   NumberUtils.toInt("", 1)   = 1
              +     *   NumberUtils.toInt("1", 0)  = 1
              +     * 
              + * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the int represented by the string, or the default if conversion fails + * @since 2.1 + */ + public static int toInt(final String str, final int defaultValue) { + if(str == null) { + return defaultValue; + } + try { + return Integer.parseInt(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + *

              Convert a String to a long, returning + * zero if the conversion fails.

              + * + *

              If the string is null, zero is returned.

              + * + *
              +     *   NumberUtils.toLong(null) = 0L
              +     *   NumberUtils.toLong("")   = 0L
              +     *   NumberUtils.toLong("1")  = 1L
              +     * 
              + * + * @param str the string to convert, may be null + * @return the long represented by the string, or 0 if + * conversion fails + * @since 2.1 + */ + public static long toLong(final String str) { + return toLong(str, 0L); + } + + /** + *

              Convert a String to a long, returning a + * default value if the conversion fails.

              + * + *

              If the string is null, the default value is returned.

              + * + *
              +     *   NumberUtils.toLong(null, 1L) = 1L
              +     *   NumberUtils.toLong("", 1L)   = 1L
              +     *   NumberUtils.toLong("1", 0L)  = 1L
              +     * 
              + * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the long represented by the string, or the default if conversion fails + * @since 2.1 + */ + public static long toLong(final String str, final long defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Long.parseLong(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + *

              Convert a String to a float, returning + * 0.0f if the conversion fails.

              + * + *

              If the string str is null, + * 0.0f is returned.

              + * + *
              +     *   NumberUtils.toFloat(null)   = 0.0f
              +     *   NumberUtils.toFloat("")     = 0.0f
              +     *   NumberUtils.toFloat("1.5")  = 1.5f
              +     * 
              + * + * @param str the string to convert, may be null + * @return the float represented by the string, or 0.0f + * if conversion fails + * @since 2.1 + */ + public static float toFloat(final String str) { + return toFloat(str, 0.0f); + } + + /** + *

              Convert a String to a float, returning a + * default value if the conversion fails.

              + * + *

              If the string str is null, the default + * value is returned.

              + * + *
              +     *   NumberUtils.toFloat(null, 1.1f)   = 1.0f
              +     *   NumberUtils.toFloat("", 1.1f)     = 1.1f
              +     *   NumberUtils.toFloat("1.5", 0.0f)  = 1.5f
              +     * 
              + * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the float represented by the string, or defaultValue + * if conversion fails + * @since 2.1 + */ + public static float toFloat(final String str, final float defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Float.parseFloat(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + *

              Convert a String to a double, returning + * 0.0d if the conversion fails.

              + * + *

              If the string str is null, + * 0.0d is returned.

              + * + *
              +     *   NumberUtils.toDouble(null)   = 0.0d
              +     *   NumberUtils.toDouble("")     = 0.0d
              +     *   NumberUtils.toDouble("1.5")  = 1.5d
              +     * 
              + * + * @param str the string to convert, may be null + * @return the double represented by the string, or 0.0d + * if conversion fails + * @since 2.1 + */ + public static double toDouble(final String str) { + return toDouble(str, 0.0d); + } + + /** + *

              Convert a String to a double, returning a + * default value if the conversion fails.

              + * + *

              If the string str is null, the default + * value is returned.

              + * + *
              +     *   NumberUtils.toDouble(null, 1.1d)   = 1.1d
              +     *   NumberUtils.toDouble("", 1.1d)     = 1.1d
              +     *   NumberUtils.toDouble("1.5", 0.0d)  = 1.5d
              +     * 
              + * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the double represented by the string, or defaultValue + * if conversion fails + * @since 2.1 + */ + public static double toDouble(final String str, final double defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Double.parseDouble(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + //----------------------------------------------------------------------- + /** + *

              Convert a String to a byte, returning + * zero if the conversion fails.

              + * + *

              If the string is null, zero is returned.

              + * + *
              +     *   NumberUtils.toByte(null) = 0
              +     *   NumberUtils.toByte("")   = 0
              +     *   NumberUtils.toByte("1")  = 1
              +     * 
              + * + * @param str the string to convert, may be null + * @return the byte represented by the string, or zero if + * conversion fails + * @since 2.5 + */ + public static byte toByte(final String str) { + return toByte(str, (byte) 0); + } + + /** + *

              Convert a String to a byte, returning a + * default value if the conversion fails.

              + * + *

              If the string is null, the default value is returned.

              + * + *
              +     *   NumberUtils.toByte(null, 1) = 1
              +     *   NumberUtils.toByte("", 1)   = 1
              +     *   NumberUtils.toByte("1", 0)  = 1
              +     * 
              + * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the byte represented by the string, or the default if conversion fails + * @since 2.5 + */ + public static byte toByte(final String str, final byte defaultValue) { + if(str == null) { + return defaultValue; + } + try { + return Byte.parseByte(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + *

              Convert a String to a short, returning + * zero if the conversion fails.

              + * + *

              If the string is null, zero is returned.

              + * + *
              +     *   NumberUtils.toShort(null) = 0
              +     *   NumberUtils.toShort("")   = 0
              +     *   NumberUtils.toShort("1")  = 1
              +     * 
              + * + * @param str the string to convert, may be null + * @return the short represented by the string, or zero if + * conversion fails + * @since 2.5 + */ + public static short toShort(final String str) { + return toShort(str, (short) 0); + } + + /** + *

              Convert a String to an short, returning a + * default value if the conversion fails.

              + * + *

              If the string is null, the default value is returned.

              + * + *
              +     *   NumberUtils.toShort(null, 1) = 1
              +     *   NumberUtils.toShort("", 1)   = 1
              +     *   NumberUtils.toShort("1", 0)  = 1
              +     * 
              + * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the short represented by the string, or the default if conversion fails + * @since 2.5 + */ + public static short toShort(final String str, final short defaultValue) { + if(str == null) { + return defaultValue; + } + try { + return Short.parseShort(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + //----------------------------------------------------------------------- + // must handle Long, Float, Integer, Float, Short, + // BigDecimal, BigInteger and Byte + // useful methods: + // Byte.decode(String) + // Byte.valueOf(String,int radix) + // Byte.valueOf(String) + // Double.valueOf(String) + // Float.valueOf(String) + // Float.valueOf(String) + // Integer.valueOf(String,int radix) + // Integer.valueOf(String) + // Integer.decode(String) + // Integer.getInteger(String) + // Integer.getInteger(String,int val) + // Integer.getInteger(String,Integer val) + // Integer.valueOf(String) + // Double.valueOf(String) + // new Byte(String) + // Long.valueOf(String) + // Long.getLong(String) + // Long.getLong(String,int) + // Long.getLong(String,Integer) + // Long.valueOf(String,int) + // Long.valueOf(String) + // Short.valueOf(String) + // Short.decode(String) + // Short.valueOf(String,int) + // Short.valueOf(String) + // new BigDecimal(String) + // new BigInteger(String) + // new BigInteger(String,int radix) + // Possible inputs: + // 45 45.5 45E7 4.5E7 Hex Oct Binary xxxF xxxD xxxf xxxd + // plus minus everything. Prolly more. A lot are not separable. + + /** + *

              Turns a string value into a java.lang.Number.

              + * + *

              If the string starts with {@code 0x} or {@code -0x} (lower or upper case) or {@code #} or {@code -#}, it + * will be interpreted as a hexadecimal Integer - or Long, if the number of digits after the + * prefix is more than 8 - or BigInteger if there are more than 16 digits. + *

              + *

              Then, the value is examined for a type qualifier on the end, i.e. one of + * 'f','F','d','D','l','L'. If it is found, it starts + * trying to create successively larger types from the type specified + * until one is found that can represent the value.

              + * + *

              If a type specifier is not found, it will check for a decimal point + * and then try successively larger types from Integer to + * BigInteger and from Float to + * BigDecimal.

              + * + *

              + * Integral values with a leading {@code 0} will be interpreted as octal; the returned number will + * be Integer, Long or BigDecimal as appropriate. + *

              + * + *

              Returns null if the string is null.

              + * + *

              This method does not trim the input string, i.e., strings with leading + * or trailing spaces will generate NumberFormatExceptions.

              + * + * @param str String containing a number, may be null + * @return Number created from the string (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted + */ + public static Number createNumber(final String str) throws NumberFormatException { + if (str == null) { + return null; + } + if (StringUtils.isBlank(str)) { + throw new NumberFormatException("A blank string is not a valid number"); + } + // Need to deal with all possible hex prefixes here + final String[] hex_prefixes = {"0x", "0X", "-0x", "-0X", "#", "-#"}; + int pfxLen = 0; + for(final String pfx : hex_prefixes) { + if (str.startsWith(pfx)) { + pfxLen += pfx.length(); + break; + } + } + if (pfxLen > 0) { // we have a hex number + char firstSigDigit = 0; // strip leading zeroes + for(int i = pfxLen; i < str.length(); i++) { + firstSigDigit = str.charAt(i); + if (firstSigDigit == '0') { // count leading zeroes + pfxLen++; + } else { + break; + } + } + final int hexDigits = str.length() - pfxLen; + if (hexDigits > 16 || (hexDigits == 16 && firstSigDigit > '7')) { // too many for Long + return createBigInteger(str); + } + if (hexDigits > 8 || (hexDigits == 8 && firstSigDigit > '7')) { // too many for an int + return createLong(str); + } + return createInteger(str); + } + final char lastChar = str.charAt(str.length() - 1); + String mant; + String dec; + String exp; + final int decPos = str.indexOf('.'); + final int expPos = str.indexOf('e') + str.indexOf('E') + 1; // assumes both not present + // if both e and E are present, this is caught by the checks on expPos (which prevent IOOBE) + // and the parsing which will detect if e or E appear in a number due to using the wrong offset + + int numDecimals = 0; // Check required precision (LANG-693) + if (decPos > -1) { // there is a decimal point + + if (expPos > -1) { // there is an exponent + if (expPos < decPos || expPos > str.length()) { // prevents double exponent causing IOOBE + throw new NumberFormatException(str + " is not a valid number."); + } + dec = str.substring(decPos + 1, expPos); + } else { + dec = str.substring(decPos + 1); + } + mant = getMantissa(str, decPos); + numDecimals = dec.length(); // gets number of digits past the decimal to ensure no loss of precision for floating point numbers. + } else { + if (expPos > -1) { + if (expPos > str.length()) { // prevents double exponent causing IOOBE + throw new NumberFormatException(str + " is not a valid number."); + } + mant = getMantissa(str, expPos); + } else { + mant = getMantissa(str); + } + dec = null; + } + if (!Character.isDigit(lastChar) && lastChar != '.') { + if (expPos > -1 && expPos < str.length() - 1) { + exp = str.substring(expPos + 1, str.length() - 1); + } else { + exp = null; + } + //Requesting a specific type.. + final String numeric = str.substring(0, str.length() - 1); + final boolean allZeros = isAllZeros(mant) && isAllZeros(exp); + switch (lastChar) { + case 'l' : + case 'L' : + if (dec == null + && exp == null + && (numeric.charAt(0) == '-' && isDigits(numeric.substring(1)) || isDigits(numeric))) { + try { + return createLong(numeric); + } catch (final NumberFormatException nfe) { // NOPMD + // Too big for a long + } + return createBigInteger(numeric); + + } + throw new NumberFormatException(str + " is not a valid number."); + case 'f' : + case 'F' : + try { + final Float f = NumberUtils.createFloat(numeric); + if (!(f.isInfinite() || (f.floatValue() == 0.0F && !allZeros))) { + //If it's too big for a float or the float value = 0 and the string + //has non-zeros in it, then float does not have the precision we want + return f; + } + + } catch (final NumberFormatException nfe) { // NOPMD + // ignore the bad number + } + //$FALL-THROUGH$ + case 'd' : + case 'D' : + try { + final Double d = NumberUtils.createDouble(numeric); + if (!(d.isInfinite() || (d.floatValue() == 0.0D && !allZeros))) { + return d; + } + } catch (final NumberFormatException nfe) { // NOPMD + // ignore the bad number + } + try { + return createBigDecimal(numeric); + } catch (final NumberFormatException e) { // NOPMD + // ignore the bad number + } + //$FALL-THROUGH$ + default : + throw new NumberFormatException(str + " is not a valid number."); + + } + } + //User doesn't have a preference on the return type, so let's start + //small and go from there... + if (expPos > -1 && expPos < str.length() - 1) { + exp = str.substring(expPos + 1, str.length()); + } else { + exp = null; + } + if (dec == null && exp == null) { // no decimal point and no exponent + //Must be an Integer, Long, Biginteger + try { + return createInteger(str); + } catch (final NumberFormatException nfe) { // NOPMD + // ignore the bad number + } + try { + return createLong(str); + } catch (final NumberFormatException nfe) { // NOPMD + // ignore the bad number + } + return createBigInteger(str); + } + + //Must be a Float, Double, BigDecimal + final boolean allZeros = isAllZeros(mant) && isAllZeros(exp); + try { + if(numDecimals <= 7){// If number has 7 or fewer digits past the decimal point then make it a float + final Float f = createFloat(str); + if (!(f.isInfinite() || (f.floatValue() == 0.0F && !allZeros))) { + return f; + } + } + } catch (final NumberFormatException nfe) { // NOPMD + // ignore the bad number + } + try { + if(numDecimals <= 16){// If number has between 8 and 16 digits past the decimal point then make it a double + final Double d = createDouble(str); + if (!(d.isInfinite() || (d.doubleValue() == 0.0D && !allZeros))) { + return d; + } + } + } catch (final NumberFormatException nfe) { // NOPMD + // ignore the bad number + } + + return createBigDecimal(str); + } + + /** + *

              Utility method for {@link #createNumber(java.lang.String)}.

              + * + *

              Returns mantissa of the given number.

              + * + * @param str the string representation of the number + * @return mantissa of the given number + */ + private static String getMantissa(final String str) { + return getMantissa(str, str.length()); + } + + /** + *

              Utility method for {@link #createNumber(java.lang.String)}.

              + * + *

              Returns mantissa of the given number.

              + * + * @param str the string representation of the number + * @param stopPos the position of the exponent or decimal point + * @return mantissa of the given number + */ + private static String getMantissa(final String str, final int stopPos) { + final char firstChar = str.charAt(0); + final boolean hasSign = (firstChar == '-' || firstChar == '+'); + + return hasSign ? str.substring(1, stopPos) : str.substring(0, stopPos); + } + + /** + *

              Utility method for {@link #createNumber(java.lang.String)}.

              + * + *

              Returns true if s is null.

              + * + * @param str the String to check + * @return if it is all zeros or null + */ + private static boolean isAllZeros(final String str) { + if (str == null) { + return true; + } + for (int i = str.length() - 1; i >= 0; i--) { + if (str.charAt(i) != '0') { + return false; + } + } + return str.length() > 0; + } + + //----------------------------------------------------------------------- + /** + *

              Convert a String to a Float.

              + * + *

              Returns null if the string is null.

              + * + * @param str a String to convert, may be null + * @return converted Float (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted + */ + public static Float createFloat(final String str) { + if (str == null) { + return null; + } + return Float.valueOf(str); + } + + /** + *

              Convert a String to a Double.

              + * + *

              Returns null if the string is null.

              + * + * @param str a String to convert, may be null + * @return converted Double (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted + */ + public static Double createDouble(final String str) { + if (str == null) { + return null; + } + return Double.valueOf(str); + } + + /** + *

              Convert a String to a Integer, handling + * hex (0xhhhh) and octal (0dddd) notations. + * N.B. a leading zero means octal; spaces are not trimmed.

              + * + *

              Returns null if the string is null.

              + * + * @param str a String to convert, may be null + * @return converted Integer (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted + */ + public static Integer createInteger(final String str) { + if (str == null) { + return null; + } + // decode() handles 0xAABD and 0777 (hex and octal) as well. + return Integer.decode(str); + } + + /** + *

              Convert a String to a Long; + * since 3.1 it handles hex (0Xhhhh) and octal (0ddd) notations. + * N.B. a leading zero means octal; spaces are not trimmed.

              + * + *

              Returns null if the string is null.

              + * + * @param str a String to convert, may be null + * @return converted Long (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted + */ + public static Long createLong(final String str) { + if (str == null) { + return null; + } + return Long.decode(str); + } + + /** + *

              Convert a String to a BigInteger; + * since 3.2 it handles hex (0x or #) and octal (0) notations.

              + * + *

              Returns null if the string is null.

              + * + * @param str a String to convert, may be null + * @return converted BigInteger (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted + */ + public static BigInteger createBigInteger(final String str) { + if (str == null) { + return null; + } + int pos = 0; // offset within string + int radix = 10; + boolean negate = false; // need to negate later? + if (str.startsWith("-")) { + negate = true; + pos = 1; + } + if (str.startsWith("0x", pos) || str.startsWith("0X", pos)) { // hex + radix = 16; + pos += 2; + } else if (str.startsWith("#", pos)) { // alternative hex (allowed by Long/Integer) + radix = 16; + pos ++; + } else if (str.startsWith("0", pos) && str.length() > pos + 1) { // octal; so long as there are additional digits + radix = 8; + pos ++; + } // default is to treat as decimal + + final BigInteger value = new BigInteger(str.substring(pos), radix); + return negate ? value.negate() : value; + } + + /** + *

              Convert a String to a BigDecimal.

              + * + *

              Returns null if the string is null.

              + * + * @param str a String to convert, may be null + * @return converted BigDecimal (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted + */ + public static BigDecimal createBigDecimal(final String str) { + if (str == null) { + return null; + } + // handle JDK1.3.1 bug where "" throws IndexOutOfBoundsException + if (StringUtils.isBlank(str)) { + throw new NumberFormatException("A blank string is not a valid number"); + } + if (str.trim().startsWith("--")) { + // this is protection for poorness in java.lang.BigDecimal. + // it accepts this as a legal value, but it does not appear + // to be in specification of class. OS X Java parses it to + // a wrong value. + throw new NumberFormatException(str + " is not a valid number."); + } + return new BigDecimal(str); + } + + // Min in array + //-------------------------------------------------------------------- + /** + *

              Returns the minimum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from min(long[]) to min(long...) + */ + public static long min(final long... array) { + // Validates input + validateArray(array); + + // Finds and returns min + long min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + + return min; + } + + /** + *

              Returns the minimum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from min(int[]) to min(int...) + */ + public static int min(final int... array) { + // Validates input + validateArray(array); + + // Finds and returns min + int min = array[0]; + for (int j = 1; j < array.length; j++) { + if (array[j] < min) { + min = array[j]; + } + } + + return min; + } + + /** + *

              Returns the minimum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from min(short[]) to min(short...) + */ + public static short min(final short... array) { + // Validates input + validateArray(array); + + // Finds and returns min + short min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + + return min; + } + + /** + *

              Returns the minimum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from min(byte[]) to min(byte...) + */ + public static byte min(final byte... array) { + // Validates input + validateArray(array); + + // Finds and returns min + byte min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + + return min; + } + + /** + *

              Returns the minimum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @see IEEE754rUtils#min(double[]) IEEE754rUtils for a version of this method that handles NaN differently + * @since 3.4 Changed signature from min(double[]) to min(double...) + */ + public static double min(final double... array) { + // Validates input + validateArray(array); + + // Finds and returns min + double min = array[0]; + for (int i = 1; i < array.length; i++) { + if (Double.isNaN(array[i])) { + return Double.NaN; + } + if (array[i] < min) { + min = array[i]; + } + } + + return min; + } + + /** + *

              Returns the minimum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @see IEEE754rUtils#min(float[]) IEEE754rUtils for a version of this method that handles NaN differently + * @since 3.4 Changed signature from min(float[]) to min(float...) + */ + public static float min(final float... array) { + // Validates input + validateArray(array); + + // Finds and returns min + float min = array[0]; + for (int i = 1; i < array.length; i++) { + if (Float.isNaN(array[i])) { + return Float.NaN; + } + if (array[i] < min) { + min = array[i]; + } + } + + return min; + } + + // Max in array + //-------------------------------------------------------------------- + /** + *

              Returns the maximum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from max(long[]) to max(long...) + */ + public static long max(final long... array) { + // Validates input + validateArray(array); + + // Finds and returns max + long max = array[0]; + for (int j = 1; j < array.length; j++) { + if (array[j] > max) { + max = array[j]; + } + } + + return max; + } + + /** + *

              Returns the maximum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from max(int[]) to max(int...) + */ + public static int max(final int... array) { + // Validates input + validateArray(array); + + // Finds and returns max + int max = array[0]; + for (int j = 1; j < array.length; j++) { + if (array[j] > max) { + max = array[j]; + } + } + + return max; + } + + /** + *

              Returns the maximum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from max(short[]) to max(short...) + */ + public static short max(final short... array) { + // Validates input + validateArray(array); + + // Finds and returns max + short max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + + return max; + } + + /** + *

              Returns the maximum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @since 3.4 Changed signature from max(byte[]) to max(byte...) + */ + public static byte max(final byte... array) { + // Validates input + validateArray(array); + + // Finds and returns max + byte max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + + return max; + } + + /** + *

              Returns the maximum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @see IEEE754rUtils#max(double[]) IEEE754rUtils for a version of this method that handles NaN differently + * @since 3.4 Changed signature from max(double[]) to max(double...) + */ + public static double max(final double... array) { + // Validates input + validateArray(array); + + // Finds and returns max + double max = array[0]; + for (int j = 1; j < array.length; j++) { + if (Double.isNaN(array[j])) { + return Double.NaN; + } + if (array[j] > max) { + max = array[j]; + } + } + + return max; + } + + /** + *

              Returns the maximum value in an array.

              + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws IllegalArgumentException if array is null + * @throws IllegalArgumentException if array is empty + * @see IEEE754rUtils#max(float[]) IEEE754rUtils for a version of this method that handles NaN differently + * @since 3.4 Changed signature from max(float[]) to max(float...) + */ + public static float max(final float... array) { + // Validates input + validateArray(array); + + // Finds and returns max + float max = array[0]; + for (int j = 1; j < array.length; j++) { + if (Float.isNaN(array[j])) { + return Float.NaN; + } + if (array[j] > max) { + max = array[j]; + } + } + + return max; + } + + /** + * Checks if the specified array is neither null nor empty. + * + * @param array the array to check + * @throws IllegalArgumentException if {@code array} is either {@code null} or empty + */ + private static void validateArray(final Object array) { + if (array == null) { + throw new IllegalArgumentException("The Array must not be null"); + } + Validate.isTrue(Array.getLength(array) != 0, "Array cannot be empty."); + } + + // 3 param min + //----------------------------------------------------------------------- + /** + *

              Gets the minimum of three long values.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static long min(long a, final long b, final long c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + *

              Gets the minimum of three int values.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static int min(int a, final int b, final int c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + *

              Gets the minimum of three short values.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static short min(short a, final short b, final short c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + *

              Gets the minimum of three byte values.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static byte min(byte a, final byte b, final byte c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + *

              Gets the minimum of three double values.

              + * + *

              If any value is NaN, NaN is + * returned. Infinity is handled.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + * @see IEEE754rUtils#min(double, double, double) for a version of this method that handles NaN differently + */ + public static double min(final double a, final double b, final double c) { + return Math.min(Math.min(a, b), c); + } + + /** + *

              Gets the minimum of three float values.

              + * + *

              If any value is NaN, NaN is + * returned. Infinity is handled.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + * @see IEEE754rUtils#min(float, float, float) for a version of this method that handles NaN differently + */ + public static float min(final float a, final float b, final float c) { + return Math.min(Math.min(a, b), c); + } + + // 3 param max + //----------------------------------------------------------------------- + /** + *

              Gets the maximum of three long values.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static long max(long a, final long b, final long c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + *

              Gets the maximum of three int values.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static int max(int a, final int b, final int c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + *

              Gets the maximum of three short values.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static short max(short a, final short b, final short c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + *

              Gets the maximum of three byte values.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static byte max(byte a, final byte b, final byte c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + *

              Gets the maximum of three double values.

              + * + *

              If any value is NaN, NaN is + * returned. Infinity is handled.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + * @see IEEE754rUtils#max(double, double, double) for a version of this method that handles NaN differently + */ + public static double max(final double a, final double b, final double c) { + return Math.max(Math.max(a, b), c); + } + + /** + *

              Gets the maximum of three float values.

              + * + *

              If any value is NaN, NaN is + * returned. Infinity is handled.

              + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + * @see IEEE754rUtils#max(float, float, float) for a version of this method that handles NaN differently + */ + public static float max(final float a, final float b, final float c) { + return Math.max(Math.max(a, b), c); + } + + //----------------------------------------------------------------------- + /** + *

              Checks whether the String contains only + * digit characters.

              + * + *

              Null and empty String will return + * false.

              + * + * @param str the String to check + * @return true if str contains only Unicode numeric + */ + public static boolean isDigits(final String str) { + if (StringUtils.isEmpty(str)) { + return false; + } + for (int i = 0; i < str.length(); i++) { + if (!Character.isDigit(str.charAt(i))) { + return false; + } + } + return true; + } + + /** + *

              Checks whether the String a valid Java number.

              + * + *

              Valid numbers include hexadecimal marked with the 0x or + * 0X qualifier, octal numbers, scientific notation and numbers + * marked with a type qualifier (e.g. 123L).

              + * + *

              Non-hexadecimal strings beginning with a leading zero are + * treated as octal values. Thus the string 09 will return + * false, since 9 is not a valid octal value. + * However, numbers beginning with {@code 0.} are treated as decimal.

              + * + *

              null and empty/blank {@code String} will return + * false.

              + * + * @param str the String to check + * @return true if the string is a correctly formatted number + * @since 3.3 the code supports hex {@code 0Xhhh} and octal {@code 0ddd} validation + */ + public static boolean isNumber(final String str) { + if (StringUtils.isEmpty(str)) { + return false; + } + final char[] chars = str.toCharArray(); + int sz = chars.length; + boolean hasExp = false; + boolean hasDecPoint = false; + boolean allowSigns = false; + boolean foundDigit = false; + // deal with any possible sign up front + final int start = (chars[0] == '-') ? 1 : 0; + if (sz > start + 1 && chars[start] == '0') { // leading 0 + if ( + (chars[start + 1] == 'x') || + (chars[start + 1] == 'X') + ) { // leading 0x/0X + int i = start + 2; + if (i == sz) { + return false; // str == "0x" + } + // checking hex (it can't be anything else) + for (; i < chars.length; i++) { + if ((chars[i] < '0' || chars[i] > '9') + && (chars[i] < 'a' || chars[i] > 'f') + && (chars[i] < 'A' || chars[i] > 'F')) { + return false; + } + } + return true; + } else if (Character.isDigit(chars[start + 1])) { + // leading 0, but not hex, must be octal + int i = start + 1; + for (; i < chars.length; i++) { + if (chars[i] < '0' || chars[i] > '7') { + return false; + } + } + return true; + } + } + sz--; // don't want to loop to the last char, check it afterwords + // for type qualifiers + int i = start; + // loop to the next to last char or to the last char if we need another digit to + // make a valid number (e.g. chars[0..5] = "1234E") + while (i < sz || (i < sz + 1 && allowSigns && !foundDigit)) { + if (chars[i] >= '0' && chars[i] <= '9') { + foundDigit = true; + allowSigns = false; + + } else if (chars[i] == '.') { + if (hasDecPoint || hasExp) { + // two decimal points or dec in exponent + return false; + } + hasDecPoint = true; + } else if (chars[i] == 'e' || chars[i] == 'E') { + // we've already taken care of hex. + if (hasExp) { + // two E's + return false; + } + if (!foundDigit) { + return false; + } + hasExp = true; + allowSigns = true; + } else if (chars[i] == '+' || chars[i] == '-') { + if (!allowSigns) { + return false; + } + allowSigns = false; + foundDigit = false; // we need a digit after the E + } else { + return false; + } + i++; + } + if (i < chars.length) { + if (chars[i] >= '0' && chars[i] <= '9') { + // no type qualifier, OK + return true; + } + if (chars[i] == 'e' || chars[i] == 'E') { + // can't have an E at the last byte + return false; + } + if (chars[i] == '.') { + if (hasDecPoint || hasExp) { + // two decimal points or dec in exponent + return false; + } + // single trailing decimal point after non-exponent is ok + return foundDigit; + } + if (!allowSigns + && (chars[i] == 'd' + || chars[i] == 'D' + || chars[i] == 'f' + || chars[i] == 'F')) { + return foundDigit; + } + if (chars[i] == 'l' + || chars[i] == 'L') { + // not allowing L with an exponent or decimal point + return foundDigit && !hasExp && !hasDecPoint; + } + // last character is illegal + return false; + } + // allowSigns is true iff the val ends in 'E' + // found digit it to make sure weird stuff like '.' and '1E-' doesn't pass + return !allowSigns && foundDigit; + } + + /** + *

              Checks whether the given String is a parsable number.

              + * + *

              Parsable numbers include those Strings understood by {@link Integer#parseInt(String)}, + * {@link Long#parseLong(String)}, {@link Float#parseFloat(String)} or + * {@link Double#parseDouble(String)}. This method can be used instead of catching {@link java.text.ParseException} + * when calling one of those methods.

              + * + *

              Hexadecimal and scientific notations are not considered parsable. + * See {@link #isNumber(String)} on those cases.

              + * + *

              {@code Null} and empty String will return false.

              + * + * @param str the String to check. + * @return {@code true} if the string is a parsable number. + * @since 3.4 + */ + public static boolean isParsable(final String str) { + if( StringUtils.endsWith( str, "." ) ) { + return false; + } + if( StringUtils.startsWith( str, "-" ) ) { + return isDigits( StringUtils.replaceOnce( str.substring(1), ".", StringUtils.EMPTY ) ); + } else { + return isDigits( StringUtils.replaceOnce( str, ".", StringUtils.EMPTY ) ); + } + } + + /** + *

              Compares two {@code int} values numerically. This is the same functionality as provided in Java 7.

              + * + * @param x the first {@code int} to compare + * @param y the second {@code int} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 + */ + public static int compare(int x, int y) { + if (x == y) { + return 0; + } + if (x < y) { + return -1; + } else { + return 1; + } + } + + /** + *

              Compares to {@code long} values numerically. This is the same functionality as provided in Java 7.

              + * + * @param x the first {@code long} to compare + * @param y the second {@code long} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 + */ + public static int compare(long x, long y) { + if (x == y) { + return 0; + } + if (x < y) { + return -1; + } else { + return 1; + } + } + + /** + *

              Compares to {@code short} values numerically. This is the same functionality as provided in Java 7.

              + * + * @param x the first {@code short} to compare + * @param y the second {@code short} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 + */ + public static int compare(short x, short y) { + if (x == y) { + return 0; + } + if (x < y) { + return -1; + } else { + return 1; + } + } + + /** + *

              Compares two {@code byte} values numerically. This is the same functionality as provided in Java 7.

              + * + * @param x the first {@code byte} to compare + * @param y the second {@code byte} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 + */ + public static int compare(byte x, byte y) { + return x-y; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/package-info.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/package-info.java new file mode 100644 index 000000000..c5c89c952 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/math/package-info.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *

              Extends {@link java.math} for business mathematical classes. + * This package is intended for business mathematical use, not scientific use. + * See Commons Math for a more complete set of mathematical classes. + * These classes are immutable, and therefore thread-safe.

              + * + *

              Although Commons Math also exists, some basic mathematical functions are contained within Lang. + * These include classes to a {@link com.fr.third.org.apache.commons.lang3.math.Fraction} class, various utilities for random numbers, and the flagship class, {@link com.fr.third.org.apache.commons.lang3.math.NumberUtils} which contains a handful of classic number functions.

              + * + *

              There are two aspects of this package that should be highlighted. + * The first is {@link com.fr.third.org.apache.commons.lang3.math.NumberUtils#createNumber(String)}, a method which does its best to convert a String into a {@link java.lang.Number} object. + * You have no idea what type of Number it will return, so you should call the relevant xxxValue method when you reach the point of needing a number. + * NumberUtils also has a related {@link com.fr.third.org.apache.commons.lang3.math.NumberUtils#isNumber(String) isNumber(String)} method.

              + * + * @since 2.0 + * @version $Id: package-info.java 1559146 2014-01-17 15:23:19Z britter $ + */ +package com.fr.third.org.apache.commons.lang3.math; diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/Mutable.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/Mutable.java new file mode 100644 index 000000000..61ca3deeb --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/Mutable.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.lang3.mutable; + +/** + * Provides mutable access to a value. + *

              + * Mutable is used as a generic interface to the implementations in this package. + *

              + * A typical use case would be to enable a primitive or string to be passed to a method and allow that method to + * effectively change the value of the primitive/string. Another use case is to store a frequently changing primitive in + * a collection (for example a total in a map) without needing to create new Integer/Long wrapper objects. + * + * @param the type to set and get + * @since 2.1 + * @version $Id: Mutable.java 1478488 2013-05-02 19:05:44Z ggregory $ + */ +public interface Mutable { + + /** + * Gets the value of this mutable. + * + * @return the stored value + */ + T getValue(); + + /** + * Sets the value of this mutable. + * + * @param value + * the value to store + * @throws NullPointerException + * if the object is null and null is invalid + * @throws ClassCastException + * if the type is invalid + */ + void setValue(T value); + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableBoolean.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableBoolean.java new file mode 100644 index 000000000..b63d9ab7f --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableBoolean.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.lang3.mutable; + +import java.io.Serializable; + +import com.fr.third.org.apache.commons.lang3.BooleanUtils; + +/** + * A mutable boolean wrapper. + *

              + * Note that as MutableBoolean does not extend Boolean, it is not treated by String.format as a Boolean parameter. + * + * @see Boolean + * @since 2.2 + * @version $Id: MutableBoolean.java 1669791 2015-03-28 15:22:59Z britter $ + */ +public class MutableBoolean implements Mutable, Serializable, Comparable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = -4830728138360036487L; + + /** The mutable value. */ + private boolean value; + + /** + * Constructs a new MutableBoolean with the default value of false. + */ + public MutableBoolean() { + super(); + } + + /** + * Constructs a new MutableBoolean with the specified value. + * + * @param value the initial value to store + */ + public MutableBoolean(final boolean value) { + super(); + this.value = value; + } + + /** + * Constructs a new MutableBoolean with the specified value. + * + * @param value the initial value to store, not null + * @throws NullPointerException if the object is null + */ + public MutableBoolean(final Boolean value) { + super(); + this.value = value.booleanValue(); + } + + //----------------------------------------------------------------------- + /** + * Gets the value as a Boolean instance. + * + * @return the value as a Boolean, never null + */ + @Override + public Boolean getValue() { + return Boolean.valueOf(this.value); + } + + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final boolean value) { + this.value = value; + } + + /** + * Sets the value to true. + * + * @since 3.3 + */ + public void setFalse() { + this.value = false; + } + + /** + * Sets the value to false. + * + * @since 3.3 + */ + public void setTrue() { + this.value = true; + } + + /** + * Sets the value from any Boolean instance. + * + * @param value the value to set, not null + * @throws NullPointerException if the object is null + */ + @Override + public void setValue(final Boolean value) { + this.value = value.booleanValue(); + } + + //----------------------------------------------------------------------- + /** + * Checks if the current value is true. + * + * @return true if the current value is true + * @since 2.5 + */ + public boolean isTrue() { + return value == true; + } + + /** + * Checks if the current value is false. + * + * @return true if the current value is false + * @since 2.5 + */ + public boolean isFalse() { + return value == false; + } + + //----------------------------------------------------------------------- + /** + * Returns the value of this MutableBoolean as a boolean. + * + * @return the boolean value represented by this object. + */ + public boolean booleanValue() { + return value; + } + + //----------------------------------------------------------------------- + /** + * Gets this mutable as an instance of Boolean. + * + * @return a Boolean instance containing the value from this mutable, never null + * @since 2.5 + */ + public Boolean toBoolean() { + return Boolean.valueOf(booleanValue()); + } + + //----------------------------------------------------------------------- + /** + * Compares this object to the specified object. The result is true if and only if the argument is + * not null and is an MutableBoolean object that contains the same + * boolean value as this object. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableBoolean) { + return value == ((MutableBoolean) obj).booleanValue(); + } + return false; + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return the hash code returned by Boolean.TRUE or Boolean.FALSE + */ + @Override + public int hashCode() { + return value ? Boolean.TRUE.hashCode() : Boolean.FALSE.hashCode(); + } + + //----------------------------------------------------------------------- + /** + * Compares this mutable to another in ascending order. + * + * @param other the other mutable to compare to, not null + * @return negative if this is less, zero if equal, positive if greater + * where false is less than true + */ + @Override + public int compareTo(final MutableBoolean other) { + return BooleanUtils.compare(this.value, other.value); + } + + //----------------------------------------------------------------------- + /** + * Returns the String value of this mutable. + * + * @return the mutable value as a string + */ + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableByte.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableByte.java new file mode 100644 index 000000000..bc70f2b14 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableByte.java @@ -0,0 +1,287 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.mutable; + +import com.fr.third.org.apache.commons.lang3.math.NumberUtils; + +/** + * A mutable byte wrapper. + *

              + * Note that as MutableByte does not extend Byte, it is not treated by String.format as a Byte parameter. + * + * @see Byte + * @since 2.1 + * @version $Id: MutableByte.java 1669791 2015-03-28 15:22:59Z britter $ + */ +public class MutableByte extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = -1585823265L; + + /** The mutable value. */ + private byte value; + + /** + * Constructs a new MutableByte with the default value of zero. + */ + public MutableByte() { + super(); + } + + /** + * Constructs a new MutableByte with the specified value. + * + * @param value the initial value to store + */ + public MutableByte(final byte value) { + super(); + this.value = value; + } + + /** + * Constructs a new MutableByte with the specified value. + * + * @param value the initial value to store, not null + * @throws NullPointerException if the object is null + */ + public MutableByte(final Number value) { + super(); + this.value = value.byteValue(); + } + + /** + * Constructs a new MutableByte parsing the given string. + * + * @param value the string to parse, not null + * @throws NumberFormatException if the string cannot be parsed into a byte + * @since 2.5 + */ + public MutableByte(final String value) throws NumberFormatException { + super(); + this.value = Byte.parseByte(value); + } + + //----------------------------------------------------------------------- + /** + * Gets the value as a Byte instance. + * + * @return the value as a Byte, never null + */ + @Override + public Byte getValue() { + return Byte.valueOf(this.value); + } + + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final byte value) { + this.value = value; + } + + /** + * Sets the value from any Number instance. + * + * @param value the value to set, not null + * @throws NullPointerException if the object is null + */ + @Override + public void setValue(final Number value) { + this.value = value.byteValue(); + } + + //----------------------------------------------------------------------- + /** + * Increments the value. + * + * @since Commons Lang 2.2 + */ + public void increment() { + value++; + } + + /** + * Decrements the value. + * + * @since Commons Lang 2.2 + */ + public void decrement() { + value--; + } + + //----------------------------------------------------------------------- + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @since Commons Lang 2.2 + */ + public void add(final byte operand) { + this.value += operand; + } + + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void add(final Number operand) { + this.value += operand.byteValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @since Commons Lang 2.2 + */ + public void subtract(final byte operand) { + this.value -= operand; + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.byteValue(); + } + + //----------------------------------------------------------------------- + // shortValue relies on Number implementation + /** + * Returns the value of this MutableByte as a byte. + * + * @return the numeric value represented by this object after conversion to type byte. + */ + @Override + public byte byteValue() { + return value; + } + + /** + * Returns the value of this MutableByte as an int. + * + * @return the numeric value represented by this object after conversion to type int. + */ + @Override + public int intValue() { + return value; + } + + /** + * Returns the value of this MutableByte as a long. + * + * @return the numeric value represented by this object after conversion to type long. + */ + @Override + public long longValue() { + return value; + } + + /** + * Returns the value of this MutableByte as a float. + * + * @return the numeric value represented by this object after conversion to type float. + */ + @Override + public float floatValue() { + return value; + } + + /** + * Returns the value of this MutableByte as a double. + * + * @return the numeric value represented by this object after conversion to type double. + */ + @Override + public double doubleValue() { + return value; + } + + //----------------------------------------------------------------------- + /** + * Gets this mutable as an instance of Byte. + * + * @return a Byte instance containing the value from this mutable + */ + public Byte toByte() { + return Byte.valueOf(byteValue()); + } + + //----------------------------------------------------------------------- + /** + * Compares this object to the specified object. The result is true if and only if the argument is + * not null and is a MutableByte object that contains the same byte value + * as this object. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableByte) { + return value == ((MutableByte) obj).byteValue(); + } + return false; + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return value; + } + + //----------------------------------------------------------------------- + /** + * Compares this mutable to another in ascending order. + * + * @param other the other mutable to compare to, not null + * @return negative if this is less, zero if equal, positive if greater + */ + @Override + public int compareTo(final MutableByte other) { + return NumberUtils.compare(this.value, other.value); + } + + //----------------------------------------------------------------------- + /** + * Returns the String value of this mutable. + * + * @return the mutable value as a string + */ + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableDouble.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableDouble.java new file mode 100644 index 000000000..7a0cfa05f --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableDouble.java @@ -0,0 +1,314 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.mutable; + +/** + * A mutable double wrapper. + *

              + * Note that as MutableDouble does not extend Double, it is not treated by String.format as a Double parameter. + * + * @see Double + * @since 2.1 + * @version $Id: MutableDouble.java 1669791 2015-03-28 15:22:59Z britter $ + */ +public class MutableDouble extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1587163916L; + + /** The mutable value. */ + private double value; + + /** + * Constructs a new MutableDouble with the default value of zero. + */ + public MutableDouble() { + super(); + } + + /** + * Constructs a new MutableDouble with the specified value. + * + * @param value the initial value to store + */ + public MutableDouble(final double value) { + super(); + this.value = value; + } + + /** + * Constructs a new MutableDouble with the specified value. + * + * @param value the initial value to store, not null + * @throws NullPointerException if the object is null + */ + public MutableDouble(final Number value) { + super(); + this.value = value.doubleValue(); + } + + /** + * Constructs a new MutableDouble parsing the given string. + * + * @param value the string to parse, not null + * @throws NumberFormatException if the string cannot be parsed into a double + * @since 2.5 + */ + public MutableDouble(final String value) throws NumberFormatException { + super(); + this.value = Double.parseDouble(value); + } + + //----------------------------------------------------------------------- + /** + * Gets the value as a Double instance. + * + * @return the value as a Double, never null + */ + @Override + public Double getValue() { + return Double.valueOf(this.value); + } + + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final double value) { + this.value = value; + } + + /** + * Sets the value from any Number instance. + * + * @param value the value to set, not null + * @throws NullPointerException if the object is null + */ + @Override + public void setValue(final Number value) { + this.value = value.doubleValue(); + } + + //----------------------------------------------------------------------- + /** + * Checks whether the double value is the special NaN value. + * + * @return true if NaN + */ + public boolean isNaN() { + return Double.isNaN(value); + } + + /** + * Checks whether the double value is infinite. + * + * @return true if infinite + */ + public boolean isInfinite() { + return Double.isInfinite(value); + } + + //----------------------------------------------------------------------- + /** + * Increments the value. + * + * @since Commons Lang 2.2 + */ + public void increment() { + value++; + } + + /** + * Decrements the value. + * + * @since Commons Lang 2.2 + */ + public void decrement() { + value--; + } + + //----------------------------------------------------------------------- + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add + * @since Commons Lang 2.2 + */ + public void add(final double operand) { + this.value += operand; + } + + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void add(final Number operand) { + this.value += operand.doubleValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @since Commons Lang 2.2 + */ + public void subtract(final double operand) { + this.value -= operand; + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.doubleValue(); + } + + //----------------------------------------------------------------------- + // shortValue and byteValue rely on Number implementation + /** + * Returns the value of this MutableDouble as an int. + * + * @return the numeric value represented by this object after conversion to type int. + */ + @Override + public int intValue() { + return (int) value; + } + + /** + * Returns the value of this MutableDouble as a long. + * + * @return the numeric value represented by this object after conversion to type long. + */ + @Override + public long longValue() { + return (long) value; + } + + /** + * Returns the value of this MutableDouble as a float. + * + * @return the numeric value represented by this object after conversion to type float. + */ + @Override + public float floatValue() { + return (float) value; + } + + /** + * Returns the value of this MutableDouble as a double. + * + * @return the numeric value represented by this object after conversion to type double. + */ + @Override + public double doubleValue() { + return value; + } + + //----------------------------------------------------------------------- + /** + * Gets this mutable as an instance of Double. + * + * @return a Double instance containing the value from this mutable, never null + */ + public Double toDouble() { + return Double.valueOf(doubleValue()); + } + + //----------------------------------------------------------------------- + /** + * Compares this object against the specified object. The result is true if and only if the argument + * is not null and is a Double object that represents a double that has the identical + * bit pattern to the bit pattern of the double represented by this object. For this purpose, two + * double values are considered to be the same if and only if the method + * {@link Double#doubleToLongBits(double)}returns the same long value when applied to each. + *

              + * Note that in most cases, for two instances of class Double,d1 and d2, + * the value of d1.equals(d2) is true if and only if

              + * + *
              +     *   d1.doubleValue() == d2.doubleValue()
              +     * 
              + * + *
              + *

              + * also has the value true. However, there are two exceptions: + *

                + *
              • If d1 and d2 both represent Double.NaN, then the + * equals method returns true, even though Double.NaN==Double.NaN has + * the value false. + *
              • If d1 represents +0.0 while d2 represents -0.0, + * or vice versa, the equal test has the value false, even though + * +0.0==-0.0 has the value true. This allows hashtables to operate properly. + *
              + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false otherwise. + */ + @Override + public boolean equals(final Object obj) { + return obj instanceof MutableDouble + && Double.doubleToLongBits(((MutableDouble) obj).value) == Double.doubleToLongBits(value); + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + final long bits = Double.doubleToLongBits(value); + return (int) (bits ^ bits >>> 32); + } + + //----------------------------------------------------------------------- + /** + * Compares this mutable to another in ascending order. + * + * @param other the other mutable to compare to, not null + * @return negative if this is less, zero if equal, positive if greater + */ + @Override + public int compareTo(final MutableDouble other) { + return Double.compare(this.value, other.value); + } + + //----------------------------------------------------------------------- + /** + * Returns the String value of this mutable. + * + * @return the mutable value as a string + */ + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableFloat.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableFloat.java new file mode 100644 index 000000000..439b2af24 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableFloat.java @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.mutable; + +/** + * A mutable float wrapper. + *

              + * Note that as MutableFloat does not extend Float, it is not treated by String.format as a Float parameter. + * + * @see Float + * @since 2.1 + * @version $Id: MutableFloat.java 1669791 2015-03-28 15:22:59Z britter $ + */ +public class MutableFloat extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 5787169186L; + + /** The mutable value. */ + private float value; + + /** + * Constructs a new MutableFloat with the default value of zero. + */ + public MutableFloat() { + super(); + } + + /** + * Constructs a new MutableFloat with the specified value. + * + * @param value the initial value to store + */ + public MutableFloat(final float value) { + super(); + this.value = value; + } + + /** + * Constructs a new MutableFloat with the specified value. + * + * @param value the initial value to store, not null + * @throws NullPointerException if the object is null + */ + public MutableFloat(final Number value) { + super(); + this.value = value.floatValue(); + } + + /** + * Constructs a new MutableFloat parsing the given string. + * + * @param value the string to parse, not null + * @throws NumberFormatException if the string cannot be parsed into a float + * @since 2.5 + */ + public MutableFloat(final String value) throws NumberFormatException { + super(); + this.value = Float.parseFloat(value); + } + + //----------------------------------------------------------------------- + /** + * Gets the value as a Float instance. + * + * @return the value as a Float, never null + */ + @Override + public Float getValue() { + return Float.valueOf(this.value); + } + + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final float value) { + this.value = value; + } + + /** + * Sets the value from any Number instance. + * + * @param value the value to set, not null + * @throws NullPointerException if the object is null + */ + @Override + public void setValue(final Number value) { + this.value = value.floatValue(); + } + + //----------------------------------------------------------------------- + /** + * Checks whether the float value is the special NaN value. + * + * @return true if NaN + */ + public boolean isNaN() { + return Float.isNaN(value); + } + + /** + * Checks whether the float value is infinite. + * + * @return true if infinite + */ + public boolean isInfinite() { + return Float.isInfinite(value); + } + + //----------------------------------------------------------------------- + /** + * Increments the value. + * + * @since Commons Lang 2.2 + */ + public void increment() { + value++; + } + + /** + * Decrements the value. + * + * @since Commons Lang 2.2 + */ + public void decrement() { + value--; + } + + //----------------------------------------------------------------------- + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @since Commons Lang 2.2 + */ + public void add(final float operand) { + this.value += operand; + } + + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void add(final Number operand) { + this.value += operand.floatValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract + * @since Commons Lang 2.2 + */ + public void subtract(final float operand) { + this.value -= operand; + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.floatValue(); + } + + //----------------------------------------------------------------------- + // shortValue and byteValue rely on Number implementation + /** + * Returns the value of this MutableFloat as an int. + * + * @return the numeric value represented by this object after conversion to type int. + */ + @Override + public int intValue() { + return (int) value; + } + + /** + * Returns the value of this MutableFloat as a long. + * + * @return the numeric value represented by this object after conversion to type long. + */ + @Override + public long longValue() { + return (long) value; + } + + /** + * Returns the value of this MutableFloat as a float. + * + * @return the numeric value represented by this object after conversion to type float. + */ + @Override + public float floatValue() { + return value; + } + + /** + * Returns the value of this MutableFloat as a double. + * + * @return the numeric value represented by this object after conversion to type double. + */ + @Override + public double doubleValue() { + return value; + } + + //----------------------------------------------------------------------- + /** + * Gets this mutable as an instance of Float. + * + * @return a Float instance containing the value from this mutable, never null + */ + public Float toFloat() { + return Float.valueOf(floatValue()); + } + + //----------------------------------------------------------------------- + /** + * Compares this object against some other object. The result is true if and only if the argument is + * not null and is a Float object that represents a float that has the + * identical bit pattern to the bit pattern of the float represented by this object. For this + * purpose, two float values are considered to be the same if and only if the method + * {@link Float#floatToIntBits(float)}returns the same int value when applied to each. + *

              + * Note that in most cases, for two instances of class Float,f1 and f2, + * the value of f1.equals(f2) is true if and only if

              + * + *
              +     *   f1.floatValue() == f2.floatValue()
              +     * 
              + * + *
              + *

              + * also has the value true. However, there are two exceptions: + *

                + *
              • If f1 and f2 both represent Float.NaN, then the + * equals method returns true, even though Float.NaN==Float.NaN has + * the value false. + *
              • If f1 represents +0.0f while f2 represents -0.0f, + * or vice versa, the equal test has the value false, even though + * 0.0f==-0.0f has the value true. + *
              + * This definition allows hashtables to operate properly. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false otherwise. + * @see java.lang.Float#floatToIntBits(float) + */ + @Override + public boolean equals(final Object obj) { + return obj instanceof MutableFloat + && Float.floatToIntBits(((MutableFloat) obj).value) == Float.floatToIntBits(value); + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return Float.floatToIntBits(value); + } + + //----------------------------------------------------------------------- + /** + * Compares this mutable to another in ascending order. + * + * @param other the other mutable to compare to, not null + * @return negative if this is less, zero if equal, positive if greater + */ + @Override + public int compareTo(final MutableFloat other) { + return Float.compare(this.value, other.value); + } + + //----------------------------------------------------------------------- + /** + * Returns the String value of this mutable. + * + * @return the mutable value as a string + */ + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableInt.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableInt.java new file mode 100644 index 000000000..9224978c5 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableInt.java @@ -0,0 +1,277 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.mutable; + +import com.fr.third.org.apache.commons.lang3.math.NumberUtils; + +/** + * A mutable int wrapper. + *

              + * Note that as MutableInt does not extend Integer, it is not treated by String.format as an Integer parameter. + * + * @see Integer + * @since 2.1 + * @version $Id: MutableInt.java 1669791 2015-03-28 15:22:59Z britter $ + */ +public class MutableInt extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 512176391864L; + + /** The mutable value. */ + private int value; + + /** + * Constructs a new MutableInt with the default value of zero. + */ + public MutableInt() { + super(); + } + + /** + * Constructs a new MutableInt with the specified value. + * + * @param value the initial value to store + */ + public MutableInt(final int value) { + super(); + this.value = value; + } + + /** + * Constructs a new MutableInt with the specified value. + * + * @param value the initial value to store, not null + * @throws NullPointerException if the object is null + */ + public MutableInt(final Number value) { + super(); + this.value = value.intValue(); + } + + /** + * Constructs a new MutableInt parsing the given string. + * + * @param value the string to parse, not null + * @throws NumberFormatException if the string cannot be parsed into an int + * @since 2.5 + */ + public MutableInt(final String value) throws NumberFormatException { + super(); + this.value = Integer.parseInt(value); + } + + //----------------------------------------------------------------------- + /** + * Gets the value as a Integer instance. + * + * @return the value as a Integer, never null + */ + @Override + public Integer getValue() { + return Integer.valueOf(this.value); + } + + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final int value) { + this.value = value; + } + + /** + * Sets the value from any Number instance. + * + * @param value the value to set, not null + * @throws NullPointerException if the object is null + */ + @Override + public void setValue(final Number value) { + this.value = value.intValue(); + } + + //----------------------------------------------------------------------- + /** + * Increments the value. + * + * @since Commons Lang 2.2 + */ + public void increment() { + value++; + } + + /** + * Decrements the value. + * + * @since Commons Lang 2.2 + */ + public void decrement() { + value--; + } + + //----------------------------------------------------------------------- + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @since Commons Lang 2.2 + */ + public void add(final int operand) { + this.value += operand; + } + + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void add(final Number operand) { + this.value += operand.intValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @since Commons Lang 2.2 + */ + public void subtract(final int operand) { + this.value -= operand; + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.intValue(); + } + + //----------------------------------------------------------------------- + // shortValue and byteValue rely on Number implementation + /** + * Returns the value of this MutableInt as an int. + * + * @return the numeric value represented by this object after conversion to type int. + */ + @Override + public int intValue() { + return value; + } + + /** + * Returns the value of this MutableInt as a long. + * + * @return the numeric value represented by this object after conversion to type long. + */ + @Override + public long longValue() { + return value; + } + + /** + * Returns the value of this MutableInt as a float. + * + * @return the numeric value represented by this object after conversion to type float. + */ + @Override + public float floatValue() { + return value; + } + + /** + * Returns the value of this MutableInt as a double. + * + * @return the numeric value represented by this object after conversion to type double. + */ + @Override + public double doubleValue() { + return value; + } + + //----------------------------------------------------------------------- + /** + * Gets this mutable as an instance of Integer. + * + * @return a Integer instance containing the value from this mutable, never null + */ + public Integer toInteger() { + return Integer.valueOf(intValue()); + } + + //----------------------------------------------------------------------- + /** + * Compares this object to the specified object. The result is true if and only if the argument is + * not null and is a MutableInt object that contains the same int value + * as this object. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableInt) { + return value == ((MutableInt) obj).intValue(); + } + return false; + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return value; + } + + //----------------------------------------------------------------------- + /** + * Compares this mutable to another in ascending order. + * + * @param other the other mutable to compare to, not null + * @return negative if this is less, zero if equal, positive if greater + */ + @Override + public int compareTo(final MutableInt other) { + return NumberUtils.compare(this.value, other.value); + } + + //----------------------------------------------------------------------- + /** + * Returns the String value of this mutable. + * + * @return the mutable value as a string + */ + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableLong.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableLong.java new file mode 100644 index 000000000..23654d19e --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableLong.java @@ -0,0 +1,277 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.mutable; + +import com.fr.third.org.apache.commons.lang3.math.NumberUtils; + +/** + * A mutable long wrapper. + *

              + * Note that as MutableLong does not extend Long, it is not treated by String.format as a Long parameter. + * + * @see Long + * @since 2.1 + * @version $Id: MutableLong.java 1669791 2015-03-28 15:22:59Z britter $ + */ +public class MutableLong extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 62986528375L; + + /** The mutable value. */ + private long value; + + /** + * Constructs a new MutableLong with the default value of zero. + */ + public MutableLong() { + super(); + } + + /** + * Constructs a new MutableLong with the specified value. + * + * @param value the initial value to store + */ + public MutableLong(final long value) { + super(); + this.value = value; + } + + /** + * Constructs a new MutableLong with the specified value. + * + * @param value the initial value to store, not null + * @throws NullPointerException if the object is null + */ + public MutableLong(final Number value) { + super(); + this.value = value.longValue(); + } + + /** + * Constructs a new MutableLong parsing the given string. + * + * @param value the string to parse, not null + * @throws NumberFormatException if the string cannot be parsed into a long + * @since 2.5 + */ + public MutableLong(final String value) throws NumberFormatException { + super(); + this.value = Long.parseLong(value); + } + + //----------------------------------------------------------------------- + /** + * Gets the value as a Long instance. + * + * @return the value as a Long, never null + */ + @Override + public Long getValue() { + return Long.valueOf(this.value); + } + + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final long value) { + this.value = value; + } + + /** + * Sets the value from any Number instance. + * + * @param value the value to set, not null + * @throws NullPointerException if the object is null + */ + @Override + public void setValue(final Number value) { + this.value = value.longValue(); + } + + //----------------------------------------------------------------------- + /** + * Increments the value. + * + * @since Commons Lang 2.2 + */ + public void increment() { + value++; + } + + /** + * Decrements the value. + * + * @since Commons Lang 2.2 + */ + public void decrement() { + value--; + } + + //----------------------------------------------------------------------- + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @since Commons Lang 2.2 + */ + public void add(final long operand) { + this.value += operand; + } + + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void add(final Number operand) { + this.value += operand.longValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @since Commons Lang 2.2 + */ + public void subtract(final long operand) { + this.value -= operand; + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.longValue(); + } + + //----------------------------------------------------------------------- + // shortValue and byteValue rely on Number implementation + /** + * Returns the value of this MutableLong as an int. + * + * @return the numeric value represented by this object after conversion to type int. + */ + @Override + public int intValue() { + return (int) value; + } + + /** + * Returns the value of this MutableLong as a long. + * + * @return the numeric value represented by this object after conversion to type long. + */ + @Override + public long longValue() { + return value; + } + + /** + * Returns the value of this MutableLong as a float. + * + * @return the numeric value represented by this object after conversion to type float. + */ + @Override + public float floatValue() { + return value; + } + + /** + * Returns the value of this MutableLong as a double. + * + * @return the numeric value represented by this object after conversion to type double. + */ + @Override + public double doubleValue() { + return value; + } + + //----------------------------------------------------------------------- + /** + * Gets this mutable as an instance of Long. + * + * @return a Long instance containing the value from this mutable, never null + */ + public Long toLong() { + return Long.valueOf(longValue()); + } + + //----------------------------------------------------------------------- + /** + * Compares this object to the specified object. The result is true if and only if the argument + * is not null and is a MutableLong object that contains the same long + * value as this object. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableLong) { + return value == ((MutableLong) obj).longValue(); + } + return false; + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return (int) (value ^ (value >>> 32)); + } + + //----------------------------------------------------------------------- + /** + * Compares this mutable to another in ascending order. + * + * @param other the other mutable to compare to, not null + * @return negative if this is less, zero if equal, positive if greater + */ + @Override + public int compareTo(final MutableLong other) { + return NumberUtils.compare(this.value, other.value); + } + + //----------------------------------------------------------------------- + /** + * Returns the String value of this mutable. + * + * @return the mutable value as a string + */ + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableObject.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableObject.java new file mode 100644 index 000000000..ca8309431 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableObject.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fr.third.org.apache.commons.lang3.mutable; + +import java.io.Serializable; + +/** + * A mutable Object wrapper. + * + * @param the type to set and get + * @since 2.1 + * @version $Id: MutableObject.java 1562971 2014-01-30 21:23:18Z ggregory $ + */ +public class MutableObject implements Mutable, Serializable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 86241875189L; + + /** The mutable value. */ + private T value; + + /** + * Constructs a new MutableObject with the default value of null. + */ + public MutableObject() { + super(); + } + + /** + * Constructs a new MutableObject with the specified value. + * + * @param value the initial value to store + */ + public MutableObject(final T value) { + super(); + this.value = value; + } + + //----------------------------------------------------------------------- + /** + * Gets the value. + * + * @return the value, may be null + */ + @Override + public T getValue() { + return this.value; + } + + /** + * Sets the value. + * + * @param value the value to set + */ + @Override + public void setValue(final T value) { + this.value = value; + } + + //----------------------------------------------------------------------- + /** + *

              + * Compares this object against the specified object. The result is true if and only if the argument + * is not null and is a MutableObject object that contains the same T + * value as this object. + *

              + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; + * true if the objects have equivalent value fields; + * false otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (this.getClass() == obj.getClass()) { + final MutableObject that = (MutableObject) obj; + return this.value.equals(that.value); + } + return false; + } + + /** + * Returns the value's hash code or 0 if the value is null. + * + * @return the value's hash code or 0 if the value is null. + */ + @Override + public int hashCode() { + return value == null ? 0 : value.hashCode(); + } + + //----------------------------------------------------------------------- + /** + * Returns the String value of this mutable. + * + * @return the mutable value as a string + */ + @Override + public String toString() { + return value == null ? "null" : value.toString(); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableShort.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableShort.java new file mode 100644 index 000000000..471cbc2b2 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/MutableShort.java @@ -0,0 +1,287 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.mutable; + +import com.fr.third.org.apache.commons.lang3.math.NumberUtils; + +/** + * A mutable short wrapper. + *

              + * Note that as MutableShort does not extend Short, it is not treated by String.format as a Short parameter. + * + * @see Short + * @since 2.1 + * @version $Id: MutableShort.java 1669791 2015-03-28 15:22:59Z britter $ + */ +public class MutableShort extends Number implements Comparable, Mutable { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = -2135791679L; + + /** The mutable value. */ + private short value; + + /** + * Constructs a new MutableShort with the default value of zero. + */ + public MutableShort() { + super(); + } + + /** + * Constructs a new MutableShort with the specified value. + * + * @param value the initial value to store + */ + public MutableShort(final short value) { + super(); + this.value = value; + } + + /** + * Constructs a new MutableShort with the specified value. + * + * @param value the initial value to store, not null + * @throws NullPointerException if the object is null + */ + public MutableShort(final Number value) { + super(); + this.value = value.shortValue(); + } + + /** + * Constructs a new MutableShort parsing the given string. + * + * @param value the string to parse, not null + * @throws NumberFormatException if the string cannot be parsed into a short + * @since 2.5 + */ + public MutableShort(final String value) throws NumberFormatException { + super(); + this.value = Short.parseShort(value); + } + + //----------------------------------------------------------------------- + /** + * Gets the value as a Short instance. + * + * @return the value as a Short, never null + */ + @Override + public Short getValue() { + return Short.valueOf(this.value); + } + + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final short value) { + this.value = value; + } + + /** + * Sets the value from any Number instance. + * + * @param value the value to set, not null + * @throws NullPointerException if the object is null + */ + @Override + public void setValue(final Number value) { + this.value = value.shortValue(); + } + + //----------------------------------------------------------------------- + /** + * Increments the value. + * + * @since Commons Lang 2.2 + */ + public void increment() { + value++; + } + + /** + * Decrements the value. + * + * @since Commons Lang 2.2 + */ + public void decrement() { + value--; + } + + //----------------------------------------------------------------------- + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @since Commons Lang 2.2 + */ + public void add(final short operand) { + this.value += operand; + } + + /** + * Adds a value to the value of this instance. + * + * @param operand the value to add, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void add(final Number operand) { + this.value += operand.shortValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @since Commons Lang 2.2 + */ + public void subtract(final short operand) { + this.value -= operand; + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @throws NullPointerException if the object is null + * @since Commons Lang 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.shortValue(); + } + + //----------------------------------------------------------------------- + // byteValue relies on Number implementation + /** + * Returns the value of this MutableShort as a short. + * + * @return the numeric value represented by this object after conversion to type short. + */ + @Override + public short shortValue() { + return value; + } + + /** + * Returns the value of this MutableShort as an int. + * + * @return the numeric value represented by this object after conversion to type int. + */ + @Override + public int intValue() { + return value; + } + + /** + * Returns the value of this MutableShort as a long. + * + * @return the numeric value represented by this object after conversion to type long. + */ + @Override + public long longValue() { + return value; + } + + /** + * Returns the value of this MutableShort as a float. + * + * @return the numeric value represented by this object after conversion to type float. + */ + @Override + public float floatValue() { + return value; + } + + /** + * Returns the value of this MutableShort as a double. + * + * @return the numeric value represented by this object after conversion to type double. + */ + @Override + public double doubleValue() { + return value; + } + + //----------------------------------------------------------------------- + /** + * Gets this mutable as an instance of Short. + * + * @return a Short instance containing the value from this mutable, never null + */ + public Short toShort() { + return Short.valueOf(shortValue()); + } + + //----------------------------------------------------------------------- + /** + * Compares this object to the specified object. The result is true if and only if the argument + * is not null and is a MutableShort object that contains the same short + * value as this object. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableShort) { + return value == ((MutableShort) obj).shortValue(); + } + return false; + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return value; + } + + //----------------------------------------------------------------------- + /** + * Compares this mutable to another in ascending order. + * + * @param other the other mutable to compare to, not null + * @return negative if this is less, zero if equal, positive if greater + */ + @Override + public int compareTo(final MutableShort other) { + return NumberUtils.compare(this.value, other.value); + } + + //----------------------------------------------------------------------- + /** + * Returns the String value of this mutable. + * + * @return the mutable value as a string + */ + @Override + public String toString() { + return String.valueOf(value); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/package-info.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/package-info.java new file mode 100644 index 000000000..b7d00d9e3 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/mutable/package-info.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *

              Provides typed mutable wrappers to primitive values and Object. + * These wrappers are similar to the wrappers provided by the Java API, but allow the wrapped value to be changed without needing to create a separate wrapper object. + * These classes are not thread-safe.

              + * + * @since 2.1 + * @version $Id: package-info.java 1559146 2014-01-17 15:23:19Z britter $ + */ +package com.fr.third.org.apache.commons.lang3.mutable; diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/package-info.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/package-info.java new file mode 100644 index 000000000..4cc89a3b5 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/package-info.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *

              Provides highly reusable static utility methods, chiefly concerned with adding value to the {@link java.lang} classes. + * Most of these classes are immutable and thus thread-safe. + * However {@link com.fr.third.org.apache.commons.lang3.CharSet} is not currently guaranteed thread-safe under all circumstances.

              + * + *

              The top level package contains various Utils classes, whilst there are various subpackages including {@link org.apache.commons.lang3.math}, {@link org.apache.commons.lang3.concurrent} and {@link org.apache.commons.lang3.builder}. + * Using the Utils classes is generally simplicity itself. + * They are the equivalent of global functions in another language, a collection of stand-alone, thread-safe, static methods. + * In contrast, subpackages may contain interfaces which may have to be implemented or classes which may need to be extended to get the full functionality from the code. + * They may, however, contain more global-like functions.

              + * + *

              Lang 3.0 requires JDK 1.5+, since Lang 3.2 it requires JDK 6+; The legacy release 2.6 requires JDK 1.2+. + * In both cases you can find features of later JDKs being maintained by us and likely to be removed or modified in favour of the JDK in the next major version. + * Note that Lang 3.0 uses a different package than its predecessors, allowing it to be used at the same time as an earlier version.

              + * + *

              You will find deprecated methods as you stroll through the Lang documentation. These are removed in the next major version.

              + * + *

              All util classes contain empty public constructors with warnings not to use. + * This may seem an odd thing to do, but it allows tools like Velocity to access the class as if it were a bean. + * In other words, yes we know about private constructors and have chosen not to use them.

              + * + *

              String manipulation - StringUtils, StringEscapeUtils, RandomStringUtils

              + * + *

              Lang has a series of String utilities. + * The first is {@link com.fr.third.org.apache.commons.lang3.StringUtils}, oodles and oodles of functions which tweak, transform, squeeze and cuddle {@link java.lang.String java.lang.Strings}. + * In addition to StringUtils, there are a series of other String manipulating classes; {@link com.fr.third.org.apache.commons.lang3.RandomStringUtils} and {@link com.fr.third.org.apache.commons.lang3.StringEscapeUtils StringEscapeUtils}. + * RandomStringUtils speaks for itself. + * It's provides ways in which to generate pieces of text, such as might be used for default passwords. + * StringEscapeUtils contains methods to escape and unescape Java, JavaScript, HTML, XML and SQL.

              + * + *

              These are ideal classes to start using if you're looking to get into Lang. + * StringUtils' {@link com.fr.third.org.apache.commons.lang3.StringUtils#capitalize(String)}, {@link com.fr.third.org.apache.commons.lang3.StringUtils#substringBetween(String, String)}/{@link com.fr.third.org.apache.commons.lang3.StringUtils#substringBefore(String, String) Before}/{@link com.fr.third.org.apache.commons.lang3.StringUtils#substringAfter(String, String) After}, {@link com.fr.third.org.apache.commons.lang3.StringUtils#split(String)} and {@link com.fr.third.org.apache.commons.lang3.StringUtils#join(Object[])} are good methods to begin with. + * If you use java.sql.Statements a lot, StringEscapeUtils.escapeSql might be of interest.

              + * + *

              Character handling - CharSetUtils, CharSet, CharRange, CharUtils

              + * + *

              In addition to dealing with Strings, it's also important to deal with chars and Characters. + * {@link com.fr.third.org.apache.commons.lang3.CharUtils} exists for this purpose, while {@link com.fr.third.org.apache.commons.lang3.CharSetUtils} exists for set-manipulation of Strings. + * Be careful, although CharSetUtils takes an argument of type String, it is only as a set of characters. + * For example, CharSetUtils.delete("testtest", "tr") will remove all t's and all r's from the String, not just the String "tr".

              + * + *

              {@link com.fr.third.org.apache.commons.lang3.CharRange} and {@link com.fr.third.org.apache.commons.lang3.CharSet} are both used internally by CharSetUtils, and will probably rarely be used.

              + * + *

              JVM interaction - SystemUtils, CharEncoding

              + * + *

              SystemUtils is a simple little class which makes it easy to find out information about which platform you are on. + * For some, this is a necessary evil. It was never something I expected to use myself until I was trying to ensure that Commons Lang itself compiled under JDK 1.2. + * Having pushed out a few JDK 1.3 bits that had slipped in (Collections.EMPTY_MAP is a classic offender), I then found that one of the Unit Tests was dying mysteriously under JDK 1.2, but ran fine under JDK 1.3. + * There was no obvious solution and I needed to move onwards, so the simple solution was to wrap that particular test in a if(SystemUtils.isJavaVersionAtLeast(1.3f)) {, make a note and move on.

              + * + *

              The {@link com.fr.third.org.apache.commons.lang3.CharEncoding} class is also used to interact with the Java environment and may be used to see which character encodings are supported in a particular environment.

              + * + *

              Serialization - SerializationUtils, SerializationException

              + * + *

              Serialization doesn't have to be that hard! + * A simple util class can take away the pain, plus it provides a method to clone an object by unserializing and reserializing, an old Java trick.

              + * + *

              Assorted functions - ObjectUtils, ClassUtils, ArrayUtils, BooleanUtils

              + * + *

              Would you believe it, {@link com.fr.third.org.apache.commons.lang3.ObjectUtils} contains handy functions for Objects, mainly null-safe implementations of the methods on {@link java.lang.Object}.

              + * + *

              {@link com.fr.third.org.apache.commons.lang3.ClassUtils} is largely a set of helper methods for reflection. + * Of special note are the comparators hidden away in ClassUtils, useful for sorting Class and Package objects by name; however they merely sort alphabetically and don't understand the common habit of sorting java and javax first.

              + * + *

              Next up, {@link com.fr.third.org.apache.commons.lang3.ArrayUtils}. + * This is a big one with many methods and many overloads of these methods so it is probably worth an in depth look here. + * Before we begin, assume that every method mentioned is overloaded for all the primitives and for Object. + * Also, the short-hand 'xxx' implies a generic primitive type, but usually also includes Object.

              + * + *
                + *
              • ArrayUtils provides singleton empty arrays for all the basic types. These will largely be of use in the Collections API with its toArray methods, but also will be of use with methods which want to return an empty array on error.
              • + *
              • add(xxx[], xxx) will add a primitive type to an array, resizing the array as you'd expect. Object is also supported.
              • + *
              • clone(xxx[]) clones a primitive or Object array.
              • + *
              • contains(xxx[], xxx) searches for a primitive or Object in a primitive or Object array.
              • + *
              • getLength(Object) returns the length of any array or an IllegalArgumentException if the parameter is not an array. hashCode(Object), equals(Object, Object), toString(Object)
              • + *
              • indexOf(xxx[], xxx) and indexOf(xxx[], xxx, int) are copies of the classic String methods, but this time for primitive/Object arrays. In addition, a lastIndexOf set of methods exists.
              • + *
              • isEmpty(xxx[]) lets you know if an array is zero-sized or null.
              • + *
              • isSameLength(xxx[], xxx[]) returns true if the arrays are the same length.
              • + *
              • Along side the add methods, there are also remove methods of two types. The first type remove the value at an index, remove(xxx[], int), while the second type remove the first value from the array, remove(xxx[], xxx).
              • + *
              • Nearing the end now. The reverse(xxx[]) method turns an array around.
              • + *
              • The subarray(xxx[], int, int) method splices an array out of a larger array.
              • + *
              • Primitive to primitive wrapper conversion is handled by the toObject(xxx[]) and toPrimitive(Xxx[]) methods.
              • + *
              + * + *

              Lastly, {@link com.fr.third.org.apache.commons.lang3.ArrayUtils#toMap(Object[])} is worthy of special note. + * It is not a heavily overloaded method for working with arrays, but a simple way to create Maps from literals.

              + * + *

              Using toMap

              + *
              + * 
              + * Map colorMap = ArrayUtils.toMap(new String[][] {{
              + *   {"RED", "#FF0000"},
              + *   {"GREEN", "#00FF00"},
              + *   {"BLUE", "#0000FF"}
              + * });
              + * 
              + * 
              + * + *

              Our final util class is {@link com.fr.third.org.apache.commons.lang3.BooleanUtils}. + * It contains various Boolean acting methods, probably of most interest is the {@link com.fr.third.org.apache.commons.lang3.BooleanUtils#toBoolean(String)} method which turns various positive/negative Strings into a Boolean object, and not just true/false as with Boolean.valueOf.

              + * + *

              Flotsam - BitField, Validate

              + *

              On reaching the end of our package, we are left with a couple of classes that haven't fit any of the topics so far.

              + *

              The {@link com.fr.third.org.apache.commons.lang3.BitField} class provides a wrapper class around the classic bitmask integer, whilst the {@link com.fr.third.org.apache.commons.lang3.Validate} class may be used for assertions (remember, we support Java 1.2).

              + * + * @since 1.0 + * @version $Id: package-info.java 1559146 2014-01-17 15:23:19Z britter $ + */ +package com.fr.third.org.apache.commons.lang3; diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/ConstructorUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/ConstructorUtils.java new file mode 100644 index 000000000..74f88db48 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/ConstructorUtils.java @@ -0,0 +1,300 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.reflect; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; +import com.fr.third.org.apache.commons.lang3.ClassUtils; +import com.fr.third.org.apache.commons.lang3.Validate; + +/** + *

              Utility reflection methods focused on constructors, modeled after + * {@link MethodUtils}.

              + * + *

              Known Limitations

              Accessing Public Constructors In A Default + * Access Superclass

              There is an issue when invoking {@code public} constructors + * contained in a default access superclass. Reflection correctly locates these + * constructors and assigns them as {@code public}. However, an + * {@link IllegalAccessException} is thrown if the constructor is + * invoked.

              + * + *

              {@link ConstructorUtils} contains a workaround for this situation: it + * will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this constructor. If this + * call succeeds, then the method can be invoked as normal. This call will only + * succeed when the application has sufficient security privileges. If this call + * fails then a warning will be logged and the method may fail.

              + * + * @since 2.5 + * @version $Id: ConstructorUtils.java 1559779 2014-01-20 17:19:02Z mbenson $ + */ +public class ConstructorUtils { + + /** + *

              ConstructorUtils instances should NOT be constructed in standard + * programming. Instead, the class should be used as + * {@code ConstructorUtils.invokeConstructor(cls, args)}.

              + * + *

              This constructor is {@code public} to permit tools that require a JavaBean + * instance to operate.

              + */ + public ConstructorUtils() { + super(); + } + + /** + *

              Returns a new instance of the specified class inferring the right constructor + * from the types of the arguments.

              + * + *

              This locates and calls a constructor. + * The constructor signature must match the argument types by assignment compatibility.

              + * + * @param the type to be constructed + * @param cls the class to be constructed, not {@code null} + * @param args the array of arguments, {@code null} treated as empty + * @return new instance of {@code cls}, not {@code null} + * + * @throws NullPointerException if {@code cls} is {@code null} + * @throws NoSuchMethodException if a matching constructor cannot be found + * @throws IllegalAccessException if invocation is not permitted by security + * @throws InvocationTargetException if an error occurs on invocation + * @throws InstantiationException if an error occurs on instantiation + * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[]) + */ + public static T invokeConstructor(final Class cls, Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, + InstantiationException { + args = ArrayUtils.nullToEmpty(args); + final Class parameterTypes[] = ClassUtils.toClass(args); + return invokeConstructor(cls, args, parameterTypes); + } + + /** + *

              Returns a new instance of the specified class choosing the right constructor + * from the list of parameter types.

              + * + *

              This locates and calls a constructor. + * The constructor signature must match the parameter types by assignment compatibility.

              + * + * @param the type to be constructed + * @param cls the class to be constructed, not {@code null} + * @param args the array of arguments, {@code null} treated as empty + * @param parameterTypes the array of parameter types, {@code null} treated as empty + * @return new instance of {@code cls}, not {@code null} + * + * @throws NullPointerException if {@code cls} is {@code null} + * @throws NoSuchMethodException if a matching constructor cannot be found + * @throws IllegalAccessException if invocation is not permitted by security + * @throws InvocationTargetException if an error occurs on invocation + * @throws InstantiationException if an error occurs on instantiation + * @see Constructor#newInstance + */ + public static T invokeConstructor(final Class cls, Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, + InstantiationException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Constructor ctor = getMatchingAccessibleConstructor(cls, parameterTypes); + if (ctor == null) { + throw new NoSuchMethodException( + "No such accessible constructor on object: " + cls.getName()); + } + return ctor.newInstance(args); + } + + /** + *

              Returns a new instance of the specified class inferring the right constructor + * from the types of the arguments.

              + * + *

              This locates and calls a constructor. + * The constructor signature must match the argument types exactly.

              + * + * @param the type to be constructed + * @param cls the class to be constructed, not {@code null} + * @param args the array of arguments, {@code null} treated as empty + * @return new instance of {@code cls}, not {@code null} + * + * @throws NullPointerException if {@code cls} is {@code null} + * @throws NoSuchMethodException if a matching constructor cannot be found + * @throws IllegalAccessException if invocation is not permitted by security + * @throws InvocationTargetException if an error occurs on invocation + * @throws InstantiationException if an error occurs on instantiation + * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[]) + */ + public static T invokeExactConstructor(final Class cls, Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, + InstantiationException { + args = ArrayUtils.nullToEmpty(args); + final Class parameterTypes[] = ClassUtils.toClass(args); + return invokeExactConstructor(cls, args, parameterTypes); + } + + /** + *

              Returns a new instance of the specified class choosing the right constructor + * from the list of parameter types.

              + * + *

              This locates and calls a constructor. + * The constructor signature must match the parameter types exactly.

              + * + * @param the type to be constructed + * @param cls the class to be constructed, not {@code null} + * @param args the array of arguments, {@code null} treated as empty + * @param parameterTypes the array of parameter types, {@code null} treated as empty + * @return new instance of cls, not {@code null} + * + * @throws NullPointerException if {@code cls} is {@code null} + * @throws NoSuchMethodException if a matching constructor cannot be found + * @throws IllegalAccessException if invocation is not permitted by security + * @throws InvocationTargetException if an error occurs on invocation + * @throws InstantiationException if an error occurs on instantiation + * @see Constructor#newInstance + */ + public static T invokeExactConstructor(final Class cls, Object[] args, + Class[] parameterTypes) throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException, InstantiationException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Constructor ctor = getAccessibleConstructor(cls, parameterTypes); + if (ctor == null) { + throw new NoSuchMethodException( + "No such accessible constructor on object: "+ cls.getName()); + } + return ctor.newInstance(args); + } + + //----------------------------------------------------------------------- + /** + *

              Finds a constructor given a class and signature, checking accessibility.

              + * + *

              This finds the constructor and ensures that it is accessible. + * The constructor signature must match the parameter types exactly.

              + * + * @param the constructor type + * @param cls the class to find a constructor for, not {@code null} + * @param parameterTypes the array of parameter types, {@code null} treated as empty + * @return the constructor, {@code null} if no matching accessible constructor found + * @see Class#getConstructor + * @see #getAccessibleConstructor(java.lang.reflect.Constructor) + * @throws NullPointerException if {@code cls} is {@code null} + */ + public static Constructor getAccessibleConstructor(final Class cls, + final Class... parameterTypes) { + Validate.notNull(cls, "class cannot be null"); + try { + return getAccessibleConstructor(cls.getConstructor(parameterTypes)); + } catch (final NoSuchMethodException e) { + return null; + } + } + + /** + *

              Checks if the specified constructor is accessible.

              + * + *

              This simply ensures that the constructor is accessible.

              + * + * @param the constructor type + * @param ctor the prototype constructor object, not {@code null} + * @return the constructor, {@code null} if no matching accessible constructor found + * @see java.lang.SecurityManager + * @throws NullPointerException if {@code ctor} is {@code null} + */ + public static Constructor getAccessibleConstructor(final Constructor ctor) { + Validate.notNull(ctor, "constructor cannot be null"); + return MemberUtils.isAccessible(ctor) + && isAccessible(ctor.getDeclaringClass()) ? ctor : null; + } + + /** + *

              Finds an accessible constructor with compatible parameters.

              + * + *

              This checks all the constructor and finds one with compatible parameters + * This requires that every parameter is assignable from the given parameter types. + * This is a more flexible search than the normal exact matching algorithm.

              + * + *

              First it checks if there is a constructor matching the exact signature. + * If not then all the constructors of the class are checked to see if their + * signatures are assignment-compatible with the parameter types. + * The first assignment-compatible matching constructor is returned.

              + * + * @param the constructor type + * @param cls the class to find a constructor for, not {@code null} + * @param parameterTypes find method with compatible parameters + * @return the constructor, null if no matching accessible constructor found + * @throws NullPointerException if {@code cls} is {@code null} + */ + public static Constructor getMatchingAccessibleConstructor(final Class cls, + final Class... parameterTypes) { + Validate.notNull(cls, "class cannot be null"); + // see if we can find the constructor directly + // most of the time this works and it's much faster + try { + final Constructor ctor = cls.getConstructor(parameterTypes); + MemberUtils.setAccessibleWorkaround(ctor); + return ctor; + } catch (final NoSuchMethodException e) { // NOPMD - Swallow + } + Constructor result = null; + /* + * (1) Class.getConstructors() is documented to return Constructor so as + * long as the array is not subsequently modified, everything's fine. + */ + final Constructor[] ctors = cls.getConstructors(); + + // return best match: + for (Constructor ctor : ctors) { + // compare parameters + if (ClassUtils.isAssignable(parameterTypes, ctor.getParameterTypes(), true)) { + // get accessible version of constructor + ctor = getAccessibleConstructor(ctor); + if (ctor != null) { + MemberUtils.setAccessibleWorkaround(ctor); + if (result == null + || MemberUtils.compareParameterTypes(ctor.getParameterTypes(), result + .getParameterTypes(), parameterTypes) < 0) { + // temporary variable for annotation, see comment above (1) + @SuppressWarnings("unchecked") + final + Constructor constructor = (Constructor)ctor; + result = constructor; + } + } + } + } + return result; + } + + /** + * Learn whether the specified class is generally accessible, i.e. is + * declared in an entirely {@code public} manner. + * @param type to check + * @return {@code true} if {@code type} and any enclosing classes are + * {@code public}. + */ + private static boolean isAccessible(final Class type) { + Class cls = type; + while (cls != null) { + if (!Modifier.isPublic(cls.getModifiers())) { + return false; + } + cls = cls.getEnclosingClass(); + } + return true; + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/FieldUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/FieldUtils.java new file mode 100644 index 000000000..451039fbb --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/FieldUtils.java @@ -0,0 +1,840 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.reflect; + +import com.fr.third.org.apache.commons.lang3.ClassUtils; +import com.fr.third.org.apache.commons.lang3.StringUtils; +import com.fr.third.org.apache.commons.lang3.Validate; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Utilities for working with {@link Field}s by reflection. Adapted and refactored from the dormant [reflect] Commons + * sandbox component. + *

              + * The ability is provided to break the scoping restrictions coded by the programmer. This can allow fields to be + * changed that shouldn't be. This facility should be used with care. + * + * @since 2.5 + * @version $Id: FieldUtils.java 1620579 2014-08-26 11:53:51Z britter $ + */ +public class FieldUtils { + + /** + * {@link FieldUtils} instances should NOT be constructed in standard programming. + *

              + * This constructor is {@code public} to permit tools that require a JavaBean instance to operate. + *

              + */ + public FieldUtils() { + super(); + } + + /** + * Gets an accessible {@link Field} by name respecting scope. Superclasses/interfaces will be considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @return the Field object + * @throws IllegalArgumentException + * if the class is {@code null}, or the field name is blank or empty + */ + public static Field getField(final Class cls, final String fieldName) { + final Field field = getField(cls, fieldName, false); + MemberUtils.setAccessibleWorkaround(field); + return field; + } + + /** + * Gets an accessible {@link Field} by name, breaking scope if requested. Superclasses/interfaces will be + * considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @return the Field object + * @throws IllegalArgumentException + * if the class is {@code null}, or the field name is blank or empty or is matched at multiple places + * in the inheritance hierarchy + */ + public static Field getField(final Class cls, final String fieldName, final boolean forceAccess) { + Validate.isTrue(cls != null, "The class must not be null"); + Validate.isTrue(StringUtils.isNotBlank(fieldName), "The field name must not be blank/empty"); + // FIXME is this workaround still needed? lang requires Java 6 + // Sun Java 1.3 has a bugged implementation of getField hence we write the + // code ourselves + + // getField() will return the Field object with the declaring class + // set correctly to the class that declares the field. Thus requesting the + // field on a subclass will return the field from the superclass. + // + // priority order for lookup: + // searchclass private/protected/package/public + // superclass protected/package/public + // private/different package blocks access to further superclasses + // implementedinterface public + + // check up the superclass hierarchy + for (Class acls = cls; acls != null; acls = acls.getSuperclass()) { + try { + final Field field = acls.getDeclaredField(fieldName); + // getDeclaredField checks for non-public scopes as well + // and it returns accurate results + if (!Modifier.isPublic(field.getModifiers())) { + if (forceAccess) { + field.setAccessible(true); + } else { + continue; + } + } + return field; + } catch (final NoSuchFieldException ex) { // NOPMD + // ignore + } + } + // check the public interface case. This must be manually searched for + // incase there is a public supersuperclass field hidden by a private/package + // superclass field. + Field match = null; + for (final Class class1 : ClassUtils.getAllInterfaces(cls)) { + try { + final Field test = class1.getField(fieldName); + Validate.isTrue(match == null, "Reference to field %s is ambiguous relative to %s" + + "; a matching field exists on two or more implemented interfaces.", fieldName, cls); + match = test; + } catch (final NoSuchFieldException ex) { // NOPMD + // ignore + } + } + return match; + } + + /** + * Gets an accessible {@link Field} by name respecting scope. Only the specified class will be considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @return the Field object + * @throws IllegalArgumentException + * if the class is {@code null}, or the field name is blank or empty + */ + public static Field getDeclaredField(final Class cls, final String fieldName) { + return getDeclaredField(cls, fieldName, false); + } + + /** + * Gets an accessible {@link Field} by name, breaking scope if requested. Only the specified class will be + * considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @return the Field object + * @throws IllegalArgumentException + * if the class is {@code null}, or the field name is blank or empty + */ + public static Field getDeclaredField(final Class cls, final String fieldName, final boolean forceAccess) { + Validate.isTrue(cls != null, "The class must not be null"); + Validate.isTrue(StringUtils.isNotBlank(fieldName), "The field name must not be blank/empty"); + try { + // only consider the specified class by using getDeclaredField() + final Field field = cls.getDeclaredField(fieldName); + if (!MemberUtils.isAccessible(field)) { + if (forceAccess) { + field.setAccessible(true); + } else { + return null; + } + } + return field; + } catch (final NoSuchFieldException e) { // NOPMD + // ignore + } + return null; + } + + /** + * Gets all fields of the given class and its parents (if any). + * + * @param cls + * the {@link Class} to query + * @return an array of Fields (possibly empty). + * @throws IllegalArgumentException + * if the class is {@code null} + * @since 3.2 + */ + public static Field[] getAllFields(final Class cls) { + final List allFieldsList = getAllFieldsList(cls); + return allFieldsList.toArray(new Field[allFieldsList.size()]); + } + + /** + * Gets all fields of the given class and its parents (if any). + * + * @param cls + * the {@link Class} to query + * @return an array of Fields (possibly empty). + * @throws IllegalArgumentException + * if the class is {@code null} + * @since 3.2 + */ + public static List getAllFieldsList(final Class cls) { + Validate.isTrue(cls != null, "The class must not be null"); + final List allFields = new ArrayList(); + Class currentClass = cls; + while (currentClass != null) { + final Field[] declaredFields = currentClass.getDeclaredFields(); + for (final Field field : declaredFields) { + allFields.add(field); + } + currentClass = currentClass.getSuperclass(); + } + return allFields; + } + + /** + * Gets all fields of the given class and its parents (if any) that are annotated with the given annotation. + * @param cls + * the {@link Class} to query + * @param annotationCls + * the {@link Annotation} that must be present on a field to be matched + * @return an array of Fields (possibly empty). + * @throws IllegalArgumentException + * if the class or annotation are {@code null} + * @since 3.4 + */ + public static Field[] getFieldsWithAnnotation(final Class cls, final Class annotationCls) { + final List annotatedFieldsList = getFieldsListWithAnnotation(cls, annotationCls); + return annotatedFieldsList.toArray(new Field[annotatedFieldsList.size()]); + } + + /** + * Gets all fields of the given class and its parents (if any) that are annotated with the given annotation. + * @param cls + * the {@link Class} to query + * @param annotationCls + * the {@link Annotation} that must be present on a field to be matched + * @return a list of Fields (possibly empty). + * @throws IllegalArgumentException + * if the class or annotation are {@code null} + * @since 3.4 + */ + public static List getFieldsListWithAnnotation(final Class cls, final Class annotationCls) { + Validate.isTrue(annotationCls != null, "The annotation class must not be null"); + final List allFields = getAllFieldsList(cls); + final List annotatedFields = new ArrayList(); + for (final Field field : allFields) { + if (field.getAnnotation(annotationCls) != null) { + annotatedFields.add(field); + } + } + return annotatedFields; + } + + /** + * Reads an accessible {@code static} {@link Field}. + * + * @param field + * to read + * @return the field value + * @throws IllegalArgumentException + * if the field is {@code null}, or not {@code static} + * @throws IllegalAccessException + * if the field is not accessible + */ + public static Object readStaticField(final Field field) throws IllegalAccessException { + return readStaticField(field, false); + } + + /** + * Reads a static {@link Field}. + * + * @param field + * to read + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. + * @return the field value + * @throws IllegalArgumentException + * if the field is {@code null} or not {@code static} + * @throws IllegalAccessException + * if the field is not made accessible + */ + public static Object readStaticField(final Field field, final boolean forceAccess) throws IllegalAccessException { + Validate.isTrue(field != null, "The field must not be null"); + Validate.isTrue(Modifier.isStatic(field.getModifiers()), "The field '%s' is not static", field.getName()); + return readField(field, (Object) null, forceAccess); + } + + /** + * Reads the named {@code public static} {@link Field}. Superclasses will be considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @return the value of the field + * @throws IllegalArgumentException + * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could + * not be found + * @throws IllegalAccessException + * if the field is not accessible + */ + public static Object readStaticField(final Class cls, final String fieldName) throws IllegalAccessException { + return readStaticField(cls, fieldName, false); + } + + /** + * Reads the named {@code static} {@link Field}. Superclasses will be considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @return the Field object + * @throws IllegalArgumentException + * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could + * not be found + * @throws IllegalAccessException + * if the field is not made accessible + */ + public static Object readStaticField(final Class cls, final String fieldName, final boolean forceAccess) throws IllegalAccessException { + final Field field = getField(cls, fieldName, forceAccess); + Validate.isTrue(field != null, "Cannot locate field '%s' on %s", fieldName, cls); + // already forced access above, don't repeat it here: + return readStaticField(field, false); + } + + /** + * Gets the value of a {@code static} {@link Field} by name. The field must be {@code public}. Only the specified + * class will be considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @return the value of the field + * @throws IllegalArgumentException + * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could + * not be found + * @throws IllegalAccessException + * if the field is not accessible + */ + public static Object readDeclaredStaticField(final Class cls, final String fieldName) throws IllegalAccessException { + return readDeclaredStaticField(cls, fieldName, false); + } + + /** + * Gets the value of a {@code static} {@link Field} by name. Only the specified class will be considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @return the Field object + * @throws IllegalArgumentException + * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could + * not be found + * @throws IllegalAccessException + * if the field is not made accessible + */ + public static Object readDeclaredStaticField(final Class cls, final String fieldName, final boolean forceAccess) throws IllegalAccessException { + final Field field = getDeclaredField(cls, fieldName, forceAccess); + Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName); + // already forced access above, don't repeat it here: + return readStaticField(field, false); + } + + /** + * Reads an accessible {@link Field}. + * + * @param field + * the field to use + * @param target + * the object to call on, may be {@code null} for {@code static} fields + * @return the field value + * @throws IllegalArgumentException + * if the field is {@code null} + * @throws IllegalAccessException + * if the field is not accessible + */ + public static Object readField(final Field field, final Object target) throws IllegalAccessException { + return readField(field, target, false); + } + + /** + * Reads a {@link Field}. + * + * @param field + * the field to use + * @param target + * the object to call on, may be {@code null} for {@code static} fields + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. + * @return the field value + * @throws IllegalArgumentException + * if the field is {@code null} + * @throws IllegalAccessException + * if the field is not made accessible + */ + public static Object readField(final Field field, final Object target, final boolean forceAccess) throws IllegalAccessException { + Validate.isTrue(field != null, "The field must not be null"); + if (forceAccess && !field.isAccessible()) { + field.setAccessible(true); + } else { + MemberUtils.setAccessibleWorkaround(field); + } + return field.get(target); + } + + /** + * Reads the named {@code public} {@link Field}. Superclasses will be considered. + * + * @param target + * the object to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @return the value of the field + * @throws IllegalArgumentException + * if the class is {@code null}, or the field name is blank or empty or could not be found + * @throws IllegalAccessException + * if the named field is not {@code public} + */ + public static Object readField(final Object target, final String fieldName) throws IllegalAccessException { + return readField(target, fieldName, false); + } + + /** + * Reads the named {@link Field}. Superclasses will be considered. + * + * @param target + * the object to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @return the field value + * @throws IllegalArgumentException + * if {@code target} is {@code null}, or the field name is blank or empty or could not be found + * @throws IllegalAccessException + * if the named field is not made accessible + */ + public static Object readField(final Object target, final String fieldName, final boolean forceAccess) throws IllegalAccessException { + Validate.isTrue(target != null, "target object must not be null"); + final Class cls = target.getClass(); + final Field field = getField(cls, fieldName, forceAccess); + Validate.isTrue(field != null, "Cannot locate field %s on %s", fieldName, cls); + // already forced access above, don't repeat it here: + return readField(field, target, false); + } + + /** + * Reads the named {@code public} {@link Field}. Only the class of the specified object will be considered. + * + * @param target + * the object to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @return the value of the field + * @throws IllegalArgumentException + * if {@code target} is {@code null}, or the field name is blank or empty or could not be found + * @throws IllegalAccessException + * if the named field is not {@code public} + */ + public static Object readDeclaredField(final Object target, final String fieldName) throws IllegalAccessException { + return readDeclaredField(target, fieldName, false); + } + + /** + * Gets a {@link Field} value by name. Only the class of the specified object will be considered. + * + * @param target + * the object to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match public fields. + * @return the Field object + * @throws IllegalArgumentException + * if {@code target} is {@code null}, or the field name is blank or empty or could not be found + * @throws IllegalAccessException + * if the field is not made accessible + */ + public static Object readDeclaredField(final Object target, final String fieldName, final boolean forceAccess) throws IllegalAccessException { + Validate.isTrue(target != null, "target object must not be null"); + final Class cls = target.getClass(); + final Field field = getDeclaredField(cls, fieldName, forceAccess); + Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls, fieldName); + // already forced access above, don't repeat it here: + return readField(field, target, false); + } + + /** + * Writes a {@code public static} {@link Field}. + * + * @param field + * to write + * @param value + * to set + * @throws IllegalArgumentException + * if the field is {@code null} or not {@code static}, or {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not {@code public} or is {@code final} + */ + public static void writeStaticField(final Field field, final Object value) throws IllegalAccessException { + writeStaticField(field, value, false); + } + + /** + * Writes a static {@link Field}. + * + * @param field + * to write + * @param value + * to set + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @throws IllegalArgumentException + * if the field is {@code null} or not {@code static}, or {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not made accessible or is {@code final} + */ + public static void writeStaticField(final Field field, final Object value, final boolean forceAccess) throws IllegalAccessException { + Validate.isTrue(field != null, "The field must not be null"); + Validate.isTrue(Modifier.isStatic(field.getModifiers()), "The field %s.%s is not static", field.getDeclaringClass().getName(), + field.getName()); + writeField(field, (Object) null, value, forceAccess); + } + + /** + * Writes a named {@code public static} {@link Field}. Superclasses will be considered. + * + * @param cls + * {@link Class} on which the field is to be found + * @param fieldName + * to write + * @param value + * to set + * @throws IllegalArgumentException + * if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is + * not {@code static}, or {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not {@code public} or is {@code final} + */ + public static void writeStaticField(final Class cls, final String fieldName, final Object value) throws IllegalAccessException { + writeStaticField(cls, fieldName, value, false); + } + + /** + * Writes a named {@code static} {@link Field}. Superclasses will be considered. + * + * @param cls + * {@link Class} on which the field is to be found + * @param fieldName + * to write + * @param value + * to set + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @throws IllegalArgumentException + * if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is + * not {@code static}, or {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not made accessible or is {@code final} + */ + public static void writeStaticField(final Class cls, final String fieldName, final Object value, final boolean forceAccess) + throws IllegalAccessException { + final Field field = getField(cls, fieldName, forceAccess); + Validate.isTrue(field != null, "Cannot locate field %s on %s", fieldName, cls); + // already forced access above, don't repeat it here: + writeStaticField(field, value, false); + } + + /** + * Writes a named {@code public static} {@link Field}. Only the specified class will be considered. + * + * @param cls + * {@link Class} on which the field is to be found + * @param fieldName + * to write + * @param value + * to set + * @throws IllegalArgumentException + * if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is + * not {@code static}, or {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not {@code public} or is {@code final} + */ + public static void writeDeclaredStaticField(final Class cls, final String fieldName, final Object value) throws IllegalAccessException { + writeDeclaredStaticField(cls, fieldName, value, false); + } + + /** + * Writes a named {@code static} {@link Field}. Only the specified class will be considered. + * + * @param cls + * {@link Class} on which the field is to be found + * @param fieldName + * to write + * @param value + * to set + * @param forceAccess + * whether to break scope restrictions using the {@code AccessibleObject#setAccessible(boolean)} method. + * {@code false} will only match {@code public} fields. + * @throws IllegalArgumentException + * if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is + * not {@code static}, or {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not made accessible or is {@code final} + */ + public static void writeDeclaredStaticField(final Class cls, final String fieldName, final Object value, final boolean forceAccess) + throws IllegalAccessException { + final Field field = getDeclaredField(cls, fieldName, forceAccess); + Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName); + // already forced access above, don't repeat it here: + writeField(field, (Object) null, value, false); + } + + /** + * Writes an accessible {@link Field}. + * + * @param field + * to write + * @param target + * the object to call on, may be {@code null} for {@code static} fields + * @param value + * to set + * @throws IllegalAccessException + * if the field or target is {@code null}, the field is not accessible or is {@code final}, or + * {@code value} is not assignable + */ + public static void writeField(final Field field, final Object target, final Object value) throws IllegalAccessException { + writeField(field, target, value, false); + } + + /** + * Writes a {@link Field}. + * + * @param field + * to write + * @param target + * the object to call on, may be {@code null} for {@code static} fields + * @param value + * to set + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @throws IllegalArgumentException + * if the field is {@code null} or {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not made accessible or is {@code final} + */ + public static void writeField(final Field field, final Object target, final Object value, final boolean forceAccess) + throws IllegalAccessException { + Validate.isTrue(field != null, "The field must not be null"); + if (forceAccess && !field.isAccessible()) { + field.setAccessible(true); + } else { + MemberUtils.setAccessibleWorkaround(field); + } + field.set(target, value); + } + + /** + * Removes the final modifier from a {@link Field}. + * + * @param field + * to remove the final modifier + * @throws IllegalArgumentException + * if the field is {@code null} + * @since 3.2 + */ + public static void removeFinalModifier(final Field field) { + removeFinalModifier(field, true); + } + + /** + * Removes the final modifier from a {@link Field}. + * + * @param field + * to remove the final modifier + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @throws IllegalArgumentException + * if the field is {@code null} + * @since 3.3 + */ + public static void removeFinalModifier(final Field field, final boolean forceAccess) { + Validate.isTrue(field != null, "The field must not be null"); + + try { + if (Modifier.isFinal(field.getModifiers())) { + // Do all JREs implement Field with a private ivar called "modifiers"? + final Field modifiersField = Field.class.getDeclaredField("modifiers"); + final boolean doForceAccess = forceAccess && !modifiersField.isAccessible(); + if (doForceAccess) { + modifiersField.setAccessible(true); + } + try { + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } finally { + if (doForceAccess) { + modifiersField.setAccessible(false); + } + } + } + } catch (final NoSuchFieldException ignored) { + // The field class contains always a modifiers field + } catch (final IllegalAccessException ignored) { + // The modifiers field is made accessible + } + } + + /** + * Writes a {@code public} {@link Field}. Superclasses will be considered. + * + * @param target + * the object to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param value + * to set + * @throws IllegalArgumentException + * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or + * {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not accessible + */ + public static void writeField(final Object target, final String fieldName, final Object value) throws IllegalAccessException { + writeField(target, fieldName, value, false); + } + + /** + * Writes a {@link Field}. Superclasses will be considered. + * + * @param target + * the object to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param value + * to set + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @throws IllegalArgumentException + * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or + * {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not made accessible + */ + public static void writeField(final Object target, final String fieldName, final Object value, final boolean forceAccess) + throws IllegalAccessException { + Validate.isTrue(target != null, "target object must not be null"); + final Class cls = target.getClass(); + final Field field = getField(cls, fieldName, forceAccess); + Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName); + // already forced access above, don't repeat it here: + writeField(field, target, value, false); + } + + /** + * Writes a {@code public} {@link Field}. Only the specified class will be considered. + * + * @param target + * the object to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param value + * to set + * @throws IllegalArgumentException + * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or + * {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not made accessible + */ + public static void writeDeclaredField(final Object target, final String fieldName, final Object value) throws IllegalAccessException { + writeDeclaredField(target, fieldName, value, false); + } + + /** + * Writes a {@code public} {@link Field}. Only the specified class will be considered. + * + * @param target + * the object to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param value + * to set + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @throws IllegalArgumentException + * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or + * {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not made accessible + */ + public static void writeDeclaredField(final Object target, final String fieldName, final Object value, final boolean forceAccess) + throws IllegalAccessException { + Validate.isTrue(target != null, "target object must not be null"); + final Class cls = target.getClass(); + final Field field = getDeclaredField(cls, fieldName, forceAccess); + Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName); + // already forced access above, don't repeat it here: + writeField(field, target, value, false); + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/InheritanceUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/InheritanceUtils.java new file mode 100644 index 000000000..41a183f7f --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/InheritanceUtils.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.reflect; + +import com.fr.third.org.apache.commons.lang3.BooleanUtils; + +/** + *

              Utility methods focusing on inheritance.

              + * + * @since 3.2 + */ +public class InheritanceUtils { + + /** + *

              {@link InheritanceUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as + * {@code MethodUtils.getAccessibleMethod(method)}.

              + * + *

              This constructor is {@code public} to permit tools that require a JavaBean + * instance to operate.

              + */ + public InheritanceUtils() { + super(); + } + + /** + *

              Returns the number of inheritance hops between two classes.

              + * + * @param child the child class, may be {@code null} + * @param parent the parent class, may be {@code null} + * @return the number of generations between the child and parent; 0 if the same class; + * -1 if the classes are not related as child and parent (includes where either class is null) + * @since 3.2 + */ + public static int distance(final Class child, final Class parent) { + if (child == null || parent == null) { + return -1; + } + + if (child.equals(parent)) { + return 0; + } + + final Class cParent = child.getSuperclass(); + int d = BooleanUtils.toInteger(parent.equals(cParent)); + + if (d == 1) { + return d; + } + d += distance(cParent, parent); + return d > 0 ? d + 1 : -1; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/MemberUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/MemberUtils.java new file mode 100644 index 000000000..38380ee05 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/MemberUtils.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.reflect; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Member; +import java.lang.reflect.Modifier; + +import com.fr.third.org.apache.commons.lang3.ClassUtils; + +/** + * Contains common code for working with {@link java.lang.reflect.Method Methods}/{@link java.lang.reflect.Constructor Constructors}, + * extracted and refactored from {@link MethodUtils} when it was imported from Commons BeanUtils. + * + * @since 2.5 + * @version $Id: MemberUtils.java 1563993 2014-02-03 18:45:16Z ggregory $ + */ +abstract class MemberUtils { + // TODO extract an interface to implement compareParameterSets(...)? + + private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE; + + /** Array of primitive number types ordered by "promotability" */ + private static final Class[] ORDERED_PRIMITIVE_TYPES = { Byte.TYPE, Short.TYPE, + Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE }; + + /** + * XXX Default access superclass workaround. + * + * When a {@code public} class has a default access superclass with {@code public} members, + * these members are accessible. Calling them from compiled code works fine. + * Unfortunately, on some JVMs, using reflection to invoke these members + * seems to (wrongly) prevent access even when the modifier is {@code public}. + * Calling {@code setAccessible(true)} solves the problem but will only work from + * sufficiently privileged code. Better workarounds would be gratefully + * accepted. + * @param o the AccessibleObject to set as accessible + * @return a boolean indicating whether the accessibility of the object was set to true. + */ + static boolean setAccessibleWorkaround(final AccessibleObject o) { + if (o == null || o.isAccessible()) { + return false; + } + final Member m = (Member) o; + if (!o.isAccessible() && Modifier.isPublic(m.getModifiers()) && isPackageAccess(m.getDeclaringClass().getModifiers())) { + try { + o.setAccessible(true); + return true; + } catch (final SecurityException e) { // NOPMD + // ignore in favor of subsequent IllegalAccessException + } + } + return false; + } + + /** + * Returns whether a given set of modifiers implies package access. + * @param modifiers to test + * @return {@code true} unless {@code package}/{@code protected}/{@code private} modifier detected + */ + static boolean isPackageAccess(final int modifiers) { + return (modifiers & ACCESS_TEST) == 0; + } + + /** + * Returns whether a {@link Member} is accessible. + * @param m Member to check + * @return {@code true} if m is accessible + */ + static boolean isAccessible(final Member m) { + return m != null && Modifier.isPublic(m.getModifiers()) && !m.isSynthetic(); + } + + /** + * Compares the relative fitness of two sets of parameter types in terms of + * matching a third set of runtime parameter types, such that a list ordered + * by the results of the comparison would return the best match first + * (least). + * + * @param left the "left" parameter set + * @param right the "right" parameter set + * @param actual the runtime parameter types to match against + * {@code left}/{@code right} + * @return int consistent with {@code compare} semantics + */ + static int compareParameterTypes(final Class[] left, final Class[] right, final Class[] actual) { + final float leftCost = getTotalTransformationCost(actual, left); + final float rightCost = getTotalTransformationCost(actual, right); + return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0; + } + + /** + * Returns the sum of the object transformation cost for each class in the + * source argument list. + * @param srcArgs The source arguments + * @param destArgs The destination arguments + * @return The total transformation cost + */ + private static float getTotalTransformationCost(final Class[] srcArgs, final Class[] destArgs) { + float totalCost = 0.0f; + for (int i = 0; i < srcArgs.length; i++) { + Class srcClass, destClass; + srcClass = srcArgs[i]; + destClass = destArgs[i]; + totalCost += getObjectTransformationCost(srcClass, destClass); + } + return totalCost; + } + + /** + * Gets the number of steps required needed to turn the source class into + * the destination class. This represents the number of steps in the object + * hierarchy graph. + * @param srcClass The source class + * @param destClass The destination class + * @return The cost of transforming an object + */ + private static float getObjectTransformationCost(Class srcClass, final Class destClass) { + if (destClass.isPrimitive()) { + return getPrimitivePromotionCost(srcClass, destClass); + } + float cost = 0.0f; + while (srcClass != null && !destClass.equals(srcClass)) { + if (destClass.isInterface() && ClassUtils.isAssignable(srcClass, destClass)) { + // slight penalty for interface match. + // we still want an exact match to override an interface match, + // but + // an interface match should override anything where we have to + // get a superclass. + cost += 0.25f; + break; + } + cost++; + srcClass = srcClass.getSuperclass(); + } + /* + * If the destination class is null, we've travelled all the way up to + * an Object match. We'll penalize this by adding 1.5 to the cost. + */ + if (srcClass == null) { + cost += 1.5f; + } + return cost; + } + + /** + * Gets the number of steps required to promote a primitive number to another + * type. + * @param srcClass the (primitive) source class + * @param destClass the (primitive) destination class + * @return The cost of promoting the primitive + */ + private static float getPrimitivePromotionCost(final Class srcClass, final Class destClass) { + float cost = 0.0f; + Class cls = srcClass; + if (!cls.isPrimitive()) { + // slight unwrapping penalty + cost += 0.1f; + cls = ClassUtils.wrapperToPrimitive(cls); + } + for (int i = 0; cls != destClass && i < ORDERED_PRIMITIVE_TYPES.length; i++) { + if (cls == ORDERED_PRIMITIVE_TYPES[i]) { + cost += 0.1f; + if (i < ORDERED_PRIMITIVE_TYPES.length - 1) { + cls = ORDERED_PRIMITIVE_TYPES[i + 1]; + } + } + } + return cost; + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/MethodUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/MethodUtils.java new file mode 100644 index 000000000..258ea851f --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/MethodUtils.java @@ -0,0 +1,639 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.reflect; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; +import com.fr.third.org.apache.commons.lang3.ClassUtils; +import com.fr.third.org.apache.commons.lang3.ClassUtils.Interfaces; +import com.fr.third.org.apache.commons.lang3.Validate; + +/** + *

              Utility reflection methods focused on {@link Method}s, originally from Commons BeanUtils. + * Differences from the BeanUtils version may be noted, especially where similar functionality + * already existed within Lang. + *

              + * + *

              Known Limitations

              + *

              Accessing Public Methods In A Default Access Superclass

              + *

              There is an issue when invoking {@code public} methods contained in a default access superclass on JREs prior to 1.4. + * Reflection locates these methods fine and correctly assigns them as {@code public}. + * However, an {@link IllegalAccessException} is thrown if the method is invoked.

              + * + *

              {@link MethodUtils} contains a workaround for this situation. + * It will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this method. + * If this call succeeds, then the method can be invoked as normal. + * This call will only succeed when the application has sufficient security privileges. + * If this call fails then the method may fail.

              + * + * @since 2.5 + * @version $Id: MethodUtils.java 1630277 2014-10-09 04:37:38Z ggregory $ + */ +public class MethodUtils { + + /** + *

              {@link MethodUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as + * {@code MethodUtils.getAccessibleMethod(method)}.

              + * + *

              This constructor is {@code public} to permit tools that require a JavaBean + * instance to operate.

              + */ + public MethodUtils() { + super(); + } + + /** + *

              Invokes a named method without parameters.

              + * + *

              This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

              + * + *

              This is a convenient wrapper for + * {@link #invokeMethod(Object object,String methodName, Object[] args, Class[] parameterTypes)}. + *

              + * + * @param object invoke method on this object + * @param methodName get method with this name + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + * + * @since 3.4 + */ + public static Object invokeMethod(final Object object, final String methodName) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + return invokeMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); + } + + /** + *

              Invokes a named method whose parameter type matches the object type.

              + * + *

              This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

              + * + *

              This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@code Boolean} object + * would match a {@code boolean} primitive.

              + * + *

              This is a convenient wrapper for + * {@link #invokeMethod(Object object,String methodName, Object[] args, Class[] parameterTypes)}. + *

              + * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + */ + public static Object invokeMethod(final Object object, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeMethod(object, methodName, args, parameterTypes); + } + + /** + *

              Invokes a named method whose parameter type matches the object type.

              + * + *

              This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

              + * + *

              This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@code Boolean} object + * would match a {@code boolean} primitive.

              + * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + */ + public static Object invokeMethod(final Object object, final String methodName, + Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + args = ArrayUtils.nullToEmpty(args); + final Method method = getMatchingAccessibleMethod(object.getClass(), + methodName, parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on object: " + + object.getClass().getName()); + } + return method.invoke(object, args); + } + + /** + *

              Invokes a method whose parameter types match exactly the object + * types.

              + * + *

              This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod}(Class,String,Class[])}.

              + * + * @param object invoke method on this object + * @param methodName get method with this name + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + * + * @since 3.4 + */ + public static Object invokeExactMethod(final Object object, final String methodName) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + return invokeExactMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); + } + + /** + *

              Invokes a method with no parameters.

              + * + *

              This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod}(Class,String,Class[])}.

              + * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactMethod(final Object object, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeExactMethod(object, methodName, args, parameterTypes); + } + + /** + *

              Invokes a method whose parameter types match exactly the parameter + * types given.

              + * + *

              This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class,String,Class[])}.

              + * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactMethod(final Object object, final String methodName, + Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Method method = getAccessibleMethod(object.getClass(), methodName, + parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on object: " + + object.getClass().getName()); + } + return method.invoke(object, args); + } + + /** + *

              Invokes a {@code static} method whose parameter types match exactly the parameter + * types given.

              + * + *

              This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class, String, Class[])}.

              + * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @param parameterTypes match these parameters - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactStaticMethod(final Class cls, final String methodName, + Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Method method = getAccessibleMethod(cls, methodName, parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on class: " + cls.getName()); + } + return method.invoke(null, args); + } + + /** + *

              Invokes a named {@code static} method whose parameter type matches the object type.

              + * + *

              This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

              + * + *

              This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@code Boolean} class + * would match a {@code boolean} primitive.

              + * + *

              This is a convenient wrapper for + * {@link #invokeStaticMethod(Class, String, Object[], Class[])}. + *

              + * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeStaticMethod(final Class cls, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeStaticMethod(cls, methodName, args, parameterTypes); + } + + /** + *

              Invokes a named {@code static} method whose parameter type matches the object type.

              + * + *

              This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

              + * + *

              This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@code Boolean} class + * would match a {@code boolean} primitive.

              + * + * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @param parameterTypes match these parameters - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeStaticMethod(final Class cls, final String methodName, + Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Method method = getMatchingAccessibleMethod(cls, methodName, + parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on class: " + cls.getName()); + } + return method.invoke(null, args); + } + + /** + *

              Invokes a {@code static} method whose parameter types match exactly the object + * types.

              + * + *

              This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class, String, Class[])}.

              + * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactStaticMethod(final Class cls, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + final Class[] parameterTypes = ClassUtils.toClass(args); + return invokeExactStaticMethod(cls, methodName, args, parameterTypes); + } + + /** + *

              Returns an accessible method (that is, one that can be invoked via + * reflection) with given name and parameters. If no such method + * can be found, return {@code null}. + * This is just a convenience wrapper for + * {@link #getAccessibleMethod(Method)}.

              + * + * @param cls get method from this class + * @param methodName get method with this name + * @param parameterTypes with these parameters types + * @return The accessible method + */ + public static Method getAccessibleMethod(final Class cls, final String methodName, + final Class... parameterTypes) { + try { + return getAccessibleMethod(cls.getMethod(methodName, + parameterTypes)); + } catch (final NoSuchMethodException e) { + return null; + } + } + + /** + *

              Returns an accessible method (that is, one that can be invoked via + * reflection) that implements the specified Method. If no such method + * can be found, return {@code null}.

              + * + * @param method The method that we wish to call + * @return The accessible method + */ + public static Method getAccessibleMethod(Method method) { + if (!MemberUtils.isAccessible(method)) { + return null; + } + // If the declaring class is public, we are done + final Class cls = method.getDeclaringClass(); + if (Modifier.isPublic(cls.getModifiers())) { + return method; + } + final String methodName = method.getName(); + final Class[] parameterTypes = method.getParameterTypes(); + + // Check the implemented interfaces and subinterfaces + method = getAccessibleMethodFromInterfaceNest(cls, methodName, + parameterTypes); + + // Check the superclass chain + if (method == null) { + method = getAccessibleMethodFromSuperclass(cls, methodName, + parameterTypes); + } + return method; + } + + /** + *

              Returns an accessible method (that is, one that can be invoked via + * reflection) by scanning through the superclasses. If no such method + * can be found, return {@code null}.

              + * + * @param cls Class to be checked + * @param methodName Method name of the method we wish to call + * @param parameterTypes The parameter type signatures + * @return the accessible method or {@code null} if not found + */ + private static Method getAccessibleMethodFromSuperclass(final Class cls, + final String methodName, final Class... parameterTypes) { + Class parentClass = cls.getSuperclass(); + while (parentClass != null) { + if (Modifier.isPublic(parentClass.getModifiers())) { + try { + return parentClass.getMethod(methodName, parameterTypes); + } catch (final NoSuchMethodException e) { + return null; + } + } + parentClass = parentClass.getSuperclass(); + } + return null; + } + + /** + *

              Returns an accessible method (that is, one that can be invoked via + * reflection) that implements the specified method, by scanning through + * all implemented interfaces and subinterfaces. If no such method + * can be found, return {@code null}.

              + * + *

              There isn't any good reason why this method must be {@code private}. + * It is because there doesn't seem any reason why other classes should + * call this rather than the higher level methods.

              + * + * @param cls Parent class for the interfaces to be checked + * @param methodName Method name of the method we wish to call + * @param parameterTypes The parameter type signatures + * @return the accessible method or {@code null} if not found + */ + private static Method getAccessibleMethodFromInterfaceNest(Class cls, + final String methodName, final Class... parameterTypes) { + // Search up the superclass chain + for (; cls != null; cls = cls.getSuperclass()) { + + // Check the implemented interfaces of the parent class + final Class[] interfaces = cls.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + // Is this interface public? + if (!Modifier.isPublic(interfaces[i].getModifiers())) { + continue; + } + // Does the method exist on this interface? + try { + return interfaces[i].getDeclaredMethod(methodName, + parameterTypes); + } catch (final NoSuchMethodException e) { // NOPMD + /* + * Swallow, if no method is found after the loop then this + * method returns null. + */ + } + // Recursively check our parent interfaces + final Method method = getAccessibleMethodFromInterfaceNest(interfaces[i], + methodName, parameterTypes); + if (method != null) { + return method; + } + } + } + return null; + } + + /** + *

              Finds an accessible method that matches the given name and has compatible parameters. + * Compatible parameters mean that every method parameter is assignable from + * the given parameters. + * In other words, it finds a method with the given name + * that will take the parameters given.

              + * + *

              This method is used by + * {@link + * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}. + *

              + * + *

              This method can match primitive parameter by passing in wrapper classes. + * For example, a {@code Boolean} will match a primitive {@code boolean} + * parameter. + *

              + * + * @param cls find method in this class + * @param methodName find method with this name + * @param parameterTypes find method with most compatible parameters + * @return The accessible method + */ + public static Method getMatchingAccessibleMethod(final Class cls, + final String methodName, final Class... parameterTypes) { + try { + final Method method = cls.getMethod(methodName, parameterTypes); + MemberUtils.setAccessibleWorkaround(method); + return method; + } catch (final NoSuchMethodException e) { // NOPMD - Swallow the exception + } + // search through all methods + Method bestMatch = null; + final Method[] methods = cls.getMethods(); + for (final Method method : methods) { + // compare name and parameters + if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) { + // get accessible version of method + final Method accessibleMethod = getAccessibleMethod(method); + if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareParameterTypes( + accessibleMethod.getParameterTypes(), + bestMatch.getParameterTypes(), + parameterTypes) < 0)) { + bestMatch = accessibleMethod; + } + } + } + if (bestMatch != null) { + MemberUtils.setAccessibleWorkaround(bestMatch); + } + return bestMatch; + } + + /** + * Get the hierarchy of overridden methods down to {@code result} respecting generics. + * @param method lowest to consider + * @param interfacesBehavior whether to search interfaces, {@code null} {@code implies} false + * @return Set<Method> in ascending order from sub- to superclass + * @throws NullPointerException if the specified method is {@code null} + * @since 3.2 + */ + public static Set getOverrideHierarchy(final Method method, final Interfaces interfacesBehavior) { + Validate.notNull(method); + final Set result = new LinkedHashSet(); + result.add(method); + + final Class[] parameterTypes = method.getParameterTypes(); + + final Class declaringClass = method.getDeclaringClass(); + + final Iterator> hierarchy = ClassUtils.hierarchy(declaringClass, interfacesBehavior).iterator(); + //skip the declaring class :P + hierarchy.next(); + hierarchyTraversal: while (hierarchy.hasNext()) { + final Class c = hierarchy.next(); + final Method m = getMatchingAccessibleMethod(c, method.getName(), parameterTypes); + if (m == null) { + continue; + } + if (Arrays.equals(m.getParameterTypes(), parameterTypes)) { + // matches without generics + result.add(m); + continue; + } + // necessary to get arguments every time in the case that we are including interfaces + final Map, Type> typeArguments = TypeUtils.getTypeArguments(declaringClass, m.getDeclaringClass()); + for (int i = 0; i < parameterTypes.length; i++) { + final Type childType = TypeUtils.unrollVariables(typeArguments, method.getGenericParameterTypes()[i]); + final Type parentType = TypeUtils.unrollVariables(typeArguments, m.getGenericParameterTypes()[i]); + if (!TypeUtils.equals(childType, parentType)) { + continue hierarchyTraversal; + } + } + result.add(m); + } + return result; + } + + /** + * Gets all methods of the given class that are annotated with the given annotation. + * @param cls + * the {@link Class} to query + * @param annotationCls + * the {@link java.lang.annotation.Annotation} that must be present on a method to be matched + * @return an array of Methods (possibly empty). + * @throws IllegalArgumentException + * if the class or annotation are {@code null} + * @since 3.4 + */ + public static Method[] getMethodsWithAnnotation(final Class cls, final Class annotationCls) { + final List annotatedMethodsList = getMethodsListWithAnnotation(cls, annotationCls); + return annotatedMethodsList.toArray(new Method[annotatedMethodsList.size()]); + } + + /** + * Gets all methods of the given class that are annotated with the given annotation. + * @param cls + * the {@link Class} to query + * @param annotationCls + * the {@link Annotation} that must be present on a method to be matched + * @return a list of Methods (possibly empty). + * @throws IllegalArgumentException + * if the class or annotation are {@code null} + * @since 3.4 + */ + public static List getMethodsListWithAnnotation(final Class cls, final Class annotationCls) { + Validate.isTrue(cls != null, "The class must not be null"); + Validate.isTrue(annotationCls != null, "The annotation class must not be null"); + final Method[] allMethods = cls.getMethods(); + final List annotatedMethods = new ArrayList(); + for (final Method method : allMethods) { + if (method.getAnnotation(annotationCls) != null) { + annotatedMethods.add(method); + } + } + return annotatedMethods; + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/TypeLiteral.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/TypeLiteral.java new file mode 100644 index 000000000..0ef373de9 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/TypeLiteral.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.reflect; + +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +import com.fr.third.org.apache.commons.lang3.Validate; + +/** + *

              Type literal comparable to {@code javax.enterprise.util.TypeLiteral}, + * made generally available outside the JEE context. Allows the passing around of + * a "token" that represents a type in a typesafe manner, as opposed to + * passing the (non-parameterized) {@link Type} object itself. Consider:

              + *

              + * You might see such a typesafe API as: + *

              + * class Typesafe {
              + *   <T> T obtain(Class<T> type, ...);
              + * }
              + * 
              + * Consumed in the manner of: + *
              + * Foo foo = typesafe.obtain(Foo.class, ...);
              + * 
              + * Yet, you run into problems when you want to do this with a parameterized type: + *
              + * List<String> listOfString = typesafe.obtain(List.class, ...); // could only give us a raw List
              + * 
              + * {@code java.lang.reflect.Type} might provide some value: + *
              + * Type listOfStringType = ...; // firstly, how to obtain this? Doable, but not straightforward.
              + * List<String> listOfString = (List<String>) typesafe.obtain(listOfStringType, ...); // nongeneric Type would necessitate a cast
              + * 
              + * The "type literal" concept was introduced to provide an alternative, i.e.: + *
              + * class Typesafe {
              + *   <T> T obtain(TypeLiteral<T> type, ...);
              + * }
              + * 
              + * Consuming code looks like: + *
              + * List<String> listOfString = typesafe.obtain(new TypeLiteral<List<String>>() {}, ...);
              + * 
              + *

              + * This has the effect of "jumping up" a level to tie a {@code java.lang.reflect.Type} + * to a type variable while simultaneously making it short work to obtain a + * {@code Type} instance for any given type, inline. + *

              + *

              Additionally {@link TypeLiteral} implements the {@link Typed} interface which + * is a generalization of this concept, and which may be implemented in custom classes. + * It is suggested that APIs be defined in terms of the interface, in the following manner: + *

              + *
              + *   <T> T obtain(Typed<T> typed, ...);
              + * 
              + * + * @version $Id: TypeLiteral.java 1606051 2014-06-27 12:22:17Z ggregory $ + * @since 3.2 + */ +public abstract class TypeLiteral implements Typed { + + @SuppressWarnings("rawtypes") + private static final TypeVariable> T = TypeLiteral.class.getTypeParameters()[0]; + + /** + * Represented type. + */ + public final Type value; + + private final String toString; + + /** + * The default constructor. + */ + protected TypeLiteral() { + this.value = + Validate.notNull(TypeUtils.getTypeArguments(getClass(), TypeLiteral.class).get(T), + "%s does not assign type parameter %s", getClass(), TypeUtils.toLongString(T)); + + this.toString = String.format("%s<%s>", TypeLiteral.class.getSimpleName(), TypeUtils.toString(value)); + } + + @Override + public final boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof TypeLiteral == false) { + return false; + } + final TypeLiteral other = (TypeLiteral) obj; + return TypeUtils.equals(value, other.value); + } + + @Override + public int hashCode() { + return 37 << 4 | value.hashCode(); + } + + @Override + public String toString() { + return toString; + } + + @Override + public Type getType() { + return value; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/TypeUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/TypeUtils.java new file mode 100644 index 000000000..e043dde58 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/TypeUtils.java @@ -0,0 +1,1848 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.reflect; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; +import com.fr.third.org.apache.commons.lang3.ClassUtils; +import com.fr.third.org.apache.commons.lang3.ObjectUtils; +import com.fr.third.org.apache.commons.lang3.Validate; +import com.fr.third.org.apache.commons.lang3.builder.Builder; + +/** + *

              Utility methods focusing on type inspection, particularly with regard to + * generics.

              + * + * @since 3.0 + * @version $Id: TypeUtils.java 1606051 2014-06-27 12:22:17Z ggregory $ + */ +public class TypeUtils { + + /** + * {@link WildcardType} builder. + * @since 3.2 + */ + public static class WildcardTypeBuilder implements Builder { + /** + * Constructor + */ + private WildcardTypeBuilder() { + } + + private Type[] upperBounds; + private Type[] lowerBounds; + + /** + * Specify upper bounds of the wildcard type to build. + * @param bounds to set + * @return {@code this} + */ + public WildcardTypeBuilder withUpperBounds(final Type... bounds) { + this.upperBounds = bounds; + return this; + } + + /** + * Specify lower bounds of the wildcard type to build. + * @param bounds to set + * @return {@code this} + */ + public WildcardTypeBuilder withLowerBounds(final Type... bounds) { + this.lowerBounds = bounds; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public WildcardType build() { + return new WildcardTypeImpl(upperBounds, lowerBounds); + } + } + + /** + * GenericArrayType implementation class. + * @since 3.2 + */ + private static final class GenericArrayTypeImpl implements GenericArrayType { + private final Type componentType; + + /** + * Constructor + * @param componentType of this array type + */ + private GenericArrayTypeImpl(final Type componentType) { + this.componentType = componentType; + } + + /** + * {@inheritDoc} + */ + @Override + public Type getGenericComponentType() { + return componentType; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return TypeUtils.toString(this); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object obj) { + return obj == this || obj instanceof GenericArrayType && TypeUtils.equals(this, (GenericArrayType) obj); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int result = 67 << 4; + result |= componentType.hashCode(); + return result; + } + } + + /** + * ParameterizedType implementation class. + * @since 3.2 + */ + private static final class ParameterizedTypeImpl implements ParameterizedType { + private final Class raw; + private final Type useOwner; + private final Type[] typeArguments; + + /** + * Constructor + * @param raw type + * @param useOwner owner type to use, if any + * @param typeArguments formal type arguments + */ + private ParameterizedTypeImpl(final Class raw, final Type useOwner, final Type[] typeArguments) { + this.raw = raw; + this.useOwner = useOwner; + this.typeArguments = typeArguments; + } + + /** + * {@inheritDoc} + */ + @Override + public Type getRawType() { + return raw; + } + + /** + * {@inheritDoc} + */ + @Override + public Type getOwnerType() { + return useOwner; + } + + /** + * {@inheritDoc} + */ + @Override + public Type[] getActualTypeArguments() { + return typeArguments.clone(); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return TypeUtils.toString(this); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object obj) { + return obj == this || obj instanceof ParameterizedType && TypeUtils.equals(this, ((ParameterizedType) obj)); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings( "deprecation" ) // ObjectUtils.hashCode(Object) has been deprecated in 3.2 + @Override + public int hashCode() { + int result = 71 << 4; + result |= raw.hashCode(); + result <<= 4; + result |= ObjectUtils.hashCode(useOwner); + result <<= 8; + result |= Arrays.hashCode(typeArguments); + return result; + } + } + + /** + * WildcardType implementation class. + * @since 3.2 + */ + private static final class WildcardTypeImpl implements WildcardType { + private static final Type[] EMPTY_BOUNDS = new Type[0]; + + private final Type[] upperBounds; + private final Type[] lowerBounds; + + /** + * Constructor + * @param upperBounds of this type + * @param lowerBounds of this type + */ + private WildcardTypeImpl(final Type[] upperBounds, final Type[] lowerBounds) { + this.upperBounds = ObjectUtils.defaultIfNull(upperBounds, EMPTY_BOUNDS); + this.lowerBounds = ObjectUtils.defaultIfNull(lowerBounds, EMPTY_BOUNDS); + } + + /** + * {@inheritDoc} + */ + @Override + public Type[] getUpperBounds() { + return upperBounds.clone(); + } + + /** + * {@inheritDoc} + */ + @Override + public Type[] getLowerBounds() { + return lowerBounds.clone(); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return TypeUtils.toString(this); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object obj) { + return obj == this || obj instanceof WildcardType && TypeUtils.equals(this, (WildcardType) obj); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int result = 73 << 8; + result |= Arrays.hashCode(upperBounds); + result <<= 8; + result |= Arrays.hashCode(lowerBounds); + return result; + } + } + + /** + * A wildcard instance matching {@code ?}. + * @since 3.2 + */ + public static final WildcardType WILDCARD_ALL = wildcardType().withUpperBounds(Object.class).build(); + + /** + *

              {@code TypeUtils} instances should NOT be constructed in standard + * programming. Instead, the class should be used as + * {@code TypeUtils.isAssignable(cls, toClass)}.

              This + * constructor is public to permit tools that require a JavaBean instance to + * operate.

              + */ + public TypeUtils() { + super(); + } + + /** + *

              Checks if the subject type may be implicitly cast to the target type + * following the Java generics rules. If both types are {@link Class} + * objects, the method returns the result of + * {@link ClassUtils#isAssignable(Class, Class)}.

              + * + * @param type the subject type to be assigned to the target type + * @param toType the target type + * @return {@code true} if {@code type} is assignable to {@code toType}. + */ + public static boolean isAssignable(final Type type, final Type toType) { + return isAssignable(type, toType, null); + } + + /** + *

              Checks if the subject type may be implicitly cast to the target type + * following the Java generics rules.

              + * + * @param type the subject type to be assigned to the target type + * @param toType the target type + * @param typeVarAssigns optional map of type variable assignments + * @return {@code true} if {@code type} is assignable to {@code toType}. + */ + private static boolean isAssignable(final Type type, final Type toType, + final Map, Type> typeVarAssigns) { + if (toType == null || toType instanceof Class) { + return isAssignable(type, (Class) toType); + } + + if (toType instanceof ParameterizedType) { + return isAssignable(type, (ParameterizedType) toType, typeVarAssigns); + } + + if (toType instanceof GenericArrayType) { + return isAssignable(type, (GenericArrayType) toType, typeVarAssigns); + } + + if (toType instanceof WildcardType) { + return isAssignable(type, (WildcardType) toType, typeVarAssigns); + } + + if (toType instanceof TypeVariable) { + return isAssignable(type, (TypeVariable) toType, typeVarAssigns); + } + + throw new IllegalStateException("found an unhandled type: " + toType); + } + + /** + *

              Checks if the subject type may be implicitly cast to the target class + * following the Java generics rules.

              + * + * @param type the subject type to be assigned to the target type + * @param toClass the target class + * @return {@code true} if {@code type} is assignable to {@code toClass}. + */ + private static boolean isAssignable(final Type type, final Class toClass) { + if (type == null) { + // consistency with ClassUtils.isAssignable() behavior + return toClass == null || !toClass.isPrimitive(); + } + + // only a null type can be assigned to null type which + // would have cause the previous to return true + if (toClass == null) { + return false; + } + + // all types are assignable to themselves + if (toClass.equals(type)) { + return true; + } + + if (type instanceof Class) { + // just comparing two classes + return ClassUtils.isAssignable((Class) type, toClass); + } + + if (type instanceof ParameterizedType) { + // only have to compare the raw type to the class + return isAssignable(getRawType((ParameterizedType) type), toClass); + } + + // * + if (type instanceof TypeVariable) { + // if any of the bounds are assignable to the class, then the + // type is assignable to the class. + for (final Type bound : ((TypeVariable) type).getBounds()) { + if (isAssignable(bound, toClass)) { + return true; + } + } + + return false; + } + + // the only classes to which a generic array type can be assigned + // are class Object and array classes + if (type instanceof GenericArrayType) { + return toClass.equals(Object.class) + || toClass.isArray() + && isAssignable(((GenericArrayType) type).getGenericComponentType(), toClass + .getComponentType()); + } + + // wildcard types are not assignable to a class (though one would think + // "? super Object" would be assignable to Object) + if (type instanceof WildcardType) { + return false; + } + + throw new IllegalStateException("found an unhandled type: " + type); + } + + /** + *

              Checks if the subject type may be implicitly cast to the target + * parameterized type following the Java generics rules.

              + * + * @param type the subject type to be assigned to the target type + * @param toParameterizedType the target parameterized type + * @param typeVarAssigns a map with type variables + * @return {@code true} if {@code type} is assignable to {@code toType}. + */ + private static boolean isAssignable(final Type type, final ParameterizedType toParameterizedType, + final Map, Type> typeVarAssigns) { + if (type == null) { + return true; + } + + // only a null type can be assigned to null type which + // would have cause the previous to return true + if (toParameterizedType == null) { + return false; + } + + // all types are assignable to themselves + if (toParameterizedType.equals(type)) { + return true; + } + + // get the target type's raw type + final Class toClass = getRawType(toParameterizedType); + // get the subject type's type arguments including owner type arguments + // and supertype arguments up to and including the target class. + final Map, Type> fromTypeVarAssigns = getTypeArguments(type, toClass, null); + + // null means the two types are not compatible + if (fromTypeVarAssigns == null) { + return false; + } + + // compatible types, but there's no type arguments. this is equivalent + // to comparing Map< ?, ? > to Map, and raw types are always assignable + // to parameterized types. + if (fromTypeVarAssigns.isEmpty()) { + return true; + } + + // get the target type's type arguments including owner type arguments + final Map, Type> toTypeVarAssigns = getTypeArguments(toParameterizedType, + toClass, typeVarAssigns); + + // now to check each type argument + for (final TypeVariable var : toTypeVarAssigns.keySet()) { + final Type toTypeArg = unrollVariableAssignments(var, toTypeVarAssigns); + final Type fromTypeArg = unrollVariableAssignments(var, fromTypeVarAssigns); + + // parameters must either be absent from the subject type, within + // the bounds of the wildcard type, or be an exact match to the + // parameters of the target type. + if (fromTypeArg != null + && !toTypeArg.equals(fromTypeArg) + && !(toTypeArg instanceof WildcardType && isAssignable(fromTypeArg, toTypeArg, + typeVarAssigns))) { + return false; + } + } + return true; + } + + /** + * Look up {@code var} in {@code typeVarAssigns} transitively, + * i.e. keep looking until the value found is not a type variable. + * @param var the type variable to look up + * @param typeVarAssigns the map used for the look up + * @return Type or {@code null} if some variable was not in the map + * @since 3.2 + */ + private static Type unrollVariableAssignments(TypeVariable var, final Map, Type> typeVarAssigns) { + Type result; + do { + result = typeVarAssigns.get(var); + if (result instanceof TypeVariable && !result.equals(var)) { + var = (TypeVariable) result; + continue; + } + break; + } while (true); + return result; + } + + /** + *

              Checks if the subject type may be implicitly cast to the target + * generic array type following the Java generics rules.

              + * + * @param type the subject type to be assigned to the target type + * @param toGenericArrayType the target generic array type + * @param typeVarAssigns a map with type variables + * @return {@code true} if {@code type} is assignable to + * {@code toGenericArrayType}. + */ + private static boolean isAssignable(final Type type, final GenericArrayType toGenericArrayType, + final Map, Type> typeVarAssigns) { + if (type == null) { + return true; + } + + // only a null type can be assigned to null type which + // would have cause the previous to return true + if (toGenericArrayType == null) { + return false; + } + + // all types are assignable to themselves + if (toGenericArrayType.equals(type)) { + return true; + } + + final Type toComponentType = toGenericArrayType.getGenericComponentType(); + + if (type instanceof Class) { + final Class cls = (Class) type; + + // compare the component types + return cls.isArray() + && isAssignable(cls.getComponentType(), toComponentType, typeVarAssigns); + } + + if (type instanceof GenericArrayType) { + // compare the component types + return isAssignable(((GenericArrayType) type).getGenericComponentType(), + toComponentType, typeVarAssigns); + } + + if (type instanceof WildcardType) { + // so long as one of the upper bounds is assignable, it's good + for (final Type bound : getImplicitUpperBounds((WildcardType) type)) { + if (isAssignable(bound, toGenericArrayType)) { + return true; + } + } + + return false; + } + + if (type instanceof TypeVariable) { + // probably should remove the following logic and just return false. + // type variables cannot specify arrays as bounds. + for (final Type bound : getImplicitBounds((TypeVariable) type)) { + if (isAssignable(bound, toGenericArrayType)) { + return true; + } + } + + return false; + } + + if (type instanceof ParameterizedType) { + // the raw type of a parameterized type is never an array or + // generic array, otherwise the declaration would look like this: + // Collection[]< ? extends String > collection; + return false; + } + + throw new IllegalStateException("found an unhandled type: " + type); + } + + /** + *

              Checks if the subject type may be implicitly cast to the target + * wildcard type following the Java generics rules.

              + * + * @param type the subject type to be assigned to the target type + * @param toWildcardType the target wildcard type + * @param typeVarAssigns a map with type variables + * @return {@code true} if {@code type} is assignable to + * {@code toWildcardType}. + */ + private static boolean isAssignable(final Type type, final WildcardType toWildcardType, + final Map, Type> typeVarAssigns) { + if (type == null) { + return true; + } + + // only a null type can be assigned to null type which + // would have cause the previous to return true + if (toWildcardType == null) { + return false; + } + + // all types are assignable to themselves + if (toWildcardType.equals(type)) { + return true; + } + + final Type[] toUpperBounds = getImplicitUpperBounds(toWildcardType); + final Type[] toLowerBounds = getImplicitLowerBounds(toWildcardType); + + if (type instanceof WildcardType) { + final WildcardType wildcardType = (WildcardType) type; + final Type[] upperBounds = getImplicitUpperBounds(wildcardType); + final Type[] lowerBounds = getImplicitLowerBounds(wildcardType); + + for (Type toBound : toUpperBounds) { + // if there are assignments for unresolved type variables, + // now's the time to substitute them. + toBound = substituteTypeVariables(toBound, typeVarAssigns); + + // each upper bound of the subject type has to be assignable to + // each + // upper bound of the target type + for (final Type bound : upperBounds) { + if (!isAssignable(bound, toBound, typeVarAssigns)) { + return false; + } + } + } + + for (Type toBound : toLowerBounds) { + // if there are assignments for unresolved type variables, + // now's the time to substitute them. + toBound = substituteTypeVariables(toBound, typeVarAssigns); + + // each lower bound of the target type has to be assignable to + // each + // lower bound of the subject type + for (final Type bound : lowerBounds) { + if (!isAssignable(toBound, bound, typeVarAssigns)) { + return false; + } + } + } + return true; + } + + for (final Type toBound : toUpperBounds) { + // if there are assignments for unresolved type variables, + // now's the time to substitute them. + if (!isAssignable(type, substituteTypeVariables(toBound, typeVarAssigns), + typeVarAssigns)) { + return false; + } + } + + for (final Type toBound : toLowerBounds) { + // if there are assignments for unresolved type variables, + // now's the time to substitute them. + if (!isAssignable(substituteTypeVariables(toBound, typeVarAssigns), type, + typeVarAssigns)) { + return false; + } + } + return true; + } + + /** + *

              Checks if the subject type may be implicitly cast to the target type + * variable following the Java generics rules.

              + * + * @param type the subject type to be assigned to the target type + * @param toTypeVariable the target type variable + * @param typeVarAssigns a map with type variables + * @return {@code true} if {@code type} is assignable to + * {@code toTypeVariable}. + */ + private static boolean isAssignable(final Type type, final TypeVariable toTypeVariable, + final Map, Type> typeVarAssigns) { + if (type == null) { + return true; + } + + // only a null type can be assigned to null type which + // would have cause the previous to return true + if (toTypeVariable == null) { + return false; + } + + // all types are assignable to themselves + if (toTypeVariable.equals(type)) { + return true; + } + + if (type instanceof TypeVariable) { + // a type variable is assignable to another type variable, if + // and only if the former is the latter, extends the latter, or + // is otherwise a descendant of the latter. + final Type[] bounds = getImplicitBounds((TypeVariable) type); + + for (final Type bound : bounds) { + if (isAssignable(bound, toTypeVariable, typeVarAssigns)) { + return true; + } + } + } + + if (type instanceof Class || type instanceof ParameterizedType + || type instanceof GenericArrayType || type instanceof WildcardType) { + return false; + } + + throw new IllegalStateException("found an unhandled type: " + type); + } + + /** + *

              Find the mapping for {@code type} in {@code typeVarAssigns}.

              + * + * @param type the type to be replaced + * @param typeVarAssigns the map with type variables + * @return the replaced type + * @throws IllegalArgumentException if the type cannot be substituted + */ + private static Type substituteTypeVariables(final Type type, final Map, Type> typeVarAssigns) { + if (type instanceof TypeVariable && typeVarAssigns != null) { + final Type replacementType = typeVarAssigns.get(type); + + if (replacementType == null) { + throw new IllegalArgumentException("missing assignment type for type variable " + + type); + } + return replacementType; + } + return type; + } + + /** + *

              Retrieves all the type arguments for this parameterized type + * including owner hierarchy arguments such as + * {@code Outer.Inner.DeepInner} . + * The arguments are returned in a + * {@link Map} specifying the argument type for each {@link TypeVariable}. + *

              + * + * @param type specifies the subject parameterized type from which to + * harvest the parameters. + * @return a {@code Map} of the type arguments to their respective type + * variables. + */ + public static Map, Type> getTypeArguments(final ParameterizedType type) { + return getTypeArguments(type, getRawType(type), null); + } + + /** + *

              Gets the type arguments of a class/interface based on a subtype. For + * instance, this method will determine that both of the parameters for the + * interface {@link Map} are {@link Object} for the subtype + * {@link java.util.Properties Properties} even though the subtype does not + * directly implement the {@code Map} interface.

              + *

              This method returns {@code null} if {@code type} is not assignable to + * {@code toClass}. It returns an empty map if none of the classes or + * interfaces in its inheritance hierarchy specify any type arguments.

              + *

              A side effect of this method is that it also retrieves the type + * arguments for the classes and interfaces that are part of the hierarchy + * between {@code type} and {@code toClass}. So with the above + * example, this method will also determine that the type arguments for + * {@link java.util.Hashtable Hashtable} are also both {@code Object}. + * In cases where the interface specified by {@code toClass} is + * (indirectly) implemented more than once (e.g. where {@code toClass} + * specifies the interface {@link java.lang.Iterable Iterable} and + * {@code type} specifies a parameterized type that implements both + * {@link java.util.Set Set} and {@link java.util.Collection Collection}), + * this method will look at the inheritance hierarchy of only one of the + * implementations/subclasses; the first interface encountered that isn't a + * subinterface to one of the others in the {@code type} to + * {@code toClass} hierarchy.

              + * + * @param type the type from which to determine the type parameters of + * {@code toClass} + * @param toClass the class whose type parameters are to be determined based + * on the subtype {@code type} + * @return a {@code Map} of the type assignments for the type variables in + * each type in the inheritance hierarchy from {@code type} to + * {@code toClass} inclusive. + */ + public static Map, Type> getTypeArguments(final Type type, final Class toClass) { + return getTypeArguments(type, toClass, null); + } + + /** + *

              Return a map of the type arguments of @{code type} in the context of {@code toClass}.

              + * + * @param type the type in question + * @param toClass the class + * @param subtypeVarAssigns a map with type variables + * @return the {@code Map} with type arguments + */ + private static Map, Type> getTypeArguments(final Type type, final Class toClass, + final Map, Type> subtypeVarAssigns) { + if (type instanceof Class) { + return getTypeArguments((Class) type, toClass, subtypeVarAssigns); + } + + if (type instanceof ParameterizedType) { + return getTypeArguments((ParameterizedType) type, toClass, subtypeVarAssigns); + } + + if (type instanceof GenericArrayType) { + return getTypeArguments(((GenericArrayType) type).getGenericComponentType(), toClass + .isArray() ? toClass.getComponentType() : toClass, subtypeVarAssigns); + } + + // since wildcard types are not assignable to classes, should this just + // return null? + if (type instanceof WildcardType) { + for (final Type bound : getImplicitUpperBounds((WildcardType) type)) { + // find the first bound that is assignable to the target class + if (isAssignable(bound, toClass)) { + return getTypeArguments(bound, toClass, subtypeVarAssigns); + } + } + + return null; + } + + if (type instanceof TypeVariable) { + for (final Type bound : getImplicitBounds((TypeVariable) type)) { + // find the first bound that is assignable to the target class + if (isAssignable(bound, toClass)) { + return getTypeArguments(bound, toClass, subtypeVarAssigns); + } + } + + return null; + } + throw new IllegalStateException("found an unhandled type: " + type); + } + + /** + *

              Return a map of the type arguments of a parameterized type in the context of {@code toClass}.

              + * + * @param parameterizedType the parameterized type + * @param toClass the class + * @param subtypeVarAssigns a map with type variables + * @return the {@code Map} with type arguments + */ + private static Map, Type> getTypeArguments( + final ParameterizedType parameterizedType, final Class toClass, + final Map, Type> subtypeVarAssigns) { + final Class cls = getRawType(parameterizedType); + + // make sure they're assignable + if (!isAssignable(cls, toClass)) { + return null; + } + + final Type ownerType = parameterizedType.getOwnerType(); + Map, Type> typeVarAssigns; + + if (ownerType instanceof ParameterizedType) { + // get the owner type arguments first + final ParameterizedType parameterizedOwnerType = (ParameterizedType) ownerType; + typeVarAssigns = getTypeArguments(parameterizedOwnerType, + getRawType(parameterizedOwnerType), subtypeVarAssigns); + } else { + // no owner, prep the type variable assignments map + typeVarAssigns = subtypeVarAssigns == null ? new HashMap, Type>() + : new HashMap, Type>(subtypeVarAssigns); + } + + // get the subject parameterized type's arguments + final Type[] typeArgs = parameterizedType.getActualTypeArguments(); + // and get the corresponding type variables from the raw class + final TypeVariable[] typeParams = cls.getTypeParameters(); + + // map the arguments to their respective type variables + for (int i = 0; i < typeParams.length; i++) { + final Type typeArg = typeArgs[i]; + typeVarAssigns.put(typeParams[i], typeVarAssigns.containsKey(typeArg) ? typeVarAssigns + .get(typeArg) : typeArg); + } + + if (toClass.equals(cls)) { + // target class has been reached. Done. + return typeVarAssigns; + } + + // walk the inheritance hierarchy until the target class is reached + return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns); + } + + /** + *

              Return a map of the type arguments of a class in the context of @{code toClass}.

              + * + * @param cls the class in question + * @param toClass the context class + * @param subtypeVarAssigns a map with type variables + * @return the {@code Map} with type arguments + */ + private static Map, Type> getTypeArguments(Class cls, final Class toClass, + final Map, Type> subtypeVarAssigns) { + // make sure they're assignable + if (!isAssignable(cls, toClass)) { + return null; + } + + // can't work with primitives + if (cls.isPrimitive()) { + // both classes are primitives? + if (toClass.isPrimitive()) { + // dealing with widening here. No type arguments to be + // harvested with these two types. + return new HashMap, Type>(); + } + + // work with wrapper the wrapper class instead of the primitive + cls = ClassUtils.primitiveToWrapper(cls); + } + + // create a copy of the incoming map, or an empty one if it's null + final HashMap, Type> typeVarAssigns = subtypeVarAssigns == null ? new HashMap, Type>() + : new HashMap, Type>(subtypeVarAssigns); + + // has target class been reached? + if (toClass.equals(cls)) { + return typeVarAssigns; + } + + // walk the inheritance hierarchy until the target class is reached + return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns); + } + + /** + *

              Tries to determine the type arguments of a class/interface based on a + * super parameterized type's type arguments. This method is the inverse of + * {@link #getTypeArguments(Type, Class)} which gets a class/interface's + * type arguments based on a subtype. It is far more limited in determining + * the type arguments for the subject class's type variables in that it can + * only determine those parameters that map from the subject {@link Class} + * object to the supertype.

              Example: {@link java.util.TreeSet + * TreeSet} sets its parameter as the parameter for + * {@link java.util.NavigableSet NavigableSet}, which in turn sets the + * parameter of {@link java.util.SortedSet}, which in turn sets the + * parameter of {@link Set}, which in turn sets the parameter of + * {@link java.util.Collection}, which in turn sets the parameter of + * {@link java.lang.Iterable}. Since {@code TreeSet}'s parameter maps + * (indirectly) to {@code Iterable}'s parameter, it will be able to + * determine that based on the super type {@code Iterable>>}, the parameter of + * {@code TreeSet} is {@code ? extends Map>}.

              + * + * @param cls the class whose type parameters are to be determined, not {@code null} + * @param superType the super type from which {@code cls}'s type + * arguments are to be determined, not {@code null} + * @return a {@code Map} of the type assignments that could be determined + * for the type variables in each type in the inheritance hierarchy from + * {@code type} to {@code toClass} inclusive. + */ + public static Map, Type> determineTypeArguments(final Class cls, + final ParameterizedType superType) { + Validate.notNull(cls, "cls is null"); + Validate.notNull(superType, "superType is null"); + + final Class superClass = getRawType(superType); + + // compatibility check + if (!isAssignable(cls, superClass)) { + return null; + } + + if (cls.equals(superClass)) { + return getTypeArguments(superType, superClass, null); + } + + // get the next class in the inheritance hierarchy + final Type midType = getClosestParentType(cls, superClass); + + // can only be a class or a parameterized type + if (midType instanceof Class) { + return determineTypeArguments((Class) midType, superType); + } + + final ParameterizedType midParameterizedType = (ParameterizedType) midType; + final Class midClass = getRawType(midParameterizedType); + // get the type variables of the mid class that map to the type + // arguments of the super class + final Map, Type> typeVarAssigns = determineTypeArguments(midClass, superType); + // map the arguments of the mid type to the class type variables + mapTypeVariablesToArguments(cls, midParameterizedType, typeVarAssigns); + + return typeVarAssigns; + } + + /** + *

              Performs a mapping of type variables.

              + * + * @param the generic type of the class in question + * @param cls the class in question + * @param parameterizedType the parameterized type + * @param typeVarAssigns the map to be filled + */ + private static void mapTypeVariablesToArguments(final Class cls, + final ParameterizedType parameterizedType, final Map, Type> typeVarAssigns) { + // capture the type variables from the owner type that have assignments + final Type ownerType = parameterizedType.getOwnerType(); + + if (ownerType instanceof ParameterizedType) { + // recursion to make sure the owner's owner type gets processed + mapTypeVariablesToArguments(cls, (ParameterizedType) ownerType, typeVarAssigns); + } + + // parameterizedType is a generic interface/class (or it's in the owner + // hierarchy of said interface/class) implemented/extended by the class + // cls. Find out which type variables of cls are type arguments of + // parameterizedType: + final Type[] typeArgs = parameterizedType.getActualTypeArguments(); + + // of the cls's type variables that are arguments of parameterizedType, + // find out which ones can be determined from the super type's arguments + final TypeVariable[] typeVars = getRawType(parameterizedType).getTypeParameters(); + + // use List view of type parameters of cls so the contains() method can be used: + final List>> typeVarList = Arrays.asList(cls + .getTypeParameters()); + + for (int i = 0; i < typeArgs.length; i++) { + final TypeVariable typeVar = typeVars[i]; + final Type typeArg = typeArgs[i]; + + // argument of parameterizedType is a type variable of cls + if (typeVarList.contains(typeArg) + // type variable of parameterizedType has an assignment in + // the super type. + && typeVarAssigns.containsKey(typeVar)) { + // map the assignment to the cls's type variable + typeVarAssigns.put((TypeVariable) typeArg, typeVarAssigns.get(typeVar)); + } + } + } + + /** + *

              Get the closest parent type to the + * super class specified by {@code superClass}.

              + * + * @param cls the class in question + * @param superClass the super class + * @return the closes parent type + */ + private static Type getClosestParentType(final Class cls, final Class superClass) { + // only look at the interfaces if the super class is also an interface + if (superClass.isInterface()) { + // get the generic interfaces of the subject class + final Type[] interfaceTypes = cls.getGenericInterfaces(); + // will hold the best generic interface match found + Type genericInterface = null; + + // find the interface closest to the super class + for (final Type midType : interfaceTypes) { + Class midClass = null; + + if (midType instanceof ParameterizedType) { + midClass = getRawType((ParameterizedType) midType); + } else if (midType instanceof Class) { + midClass = (Class) midType; + } else { + throw new IllegalStateException("Unexpected generic" + + " interface type found: " + midType); + } + + // check if this interface is further up the inheritance chain + // than the previously found match + if (isAssignable(midClass, superClass) + && isAssignable(genericInterface, (Type) midClass)) { + genericInterface = midType; + } + } + + // found a match? + if (genericInterface != null) { + return genericInterface; + } + } + + // none of the interfaces were descendants of the target class, so the + // super class has to be one, instead + return cls.getGenericSuperclass(); + } + + /** + *

              Checks if the given value can be assigned to the target type + * following the Java generics rules.

              + * + * @param value the value to be checked + * @param type the target type + * @return {@code true} if {@code value} is an instance of {@code type}. + */ + public static boolean isInstance(final Object value, final Type type) { + if (type == null) { + return false; + } + + return value == null ? !(type instanceof Class) || !((Class) type).isPrimitive() + : isAssignable(value.getClass(), type, null); + } + + /** + *

              This method strips out the redundant upper bound types in type + * variable types and wildcard types (or it would with wildcard types if + * multiple upper bounds were allowed).

              Example, with the variable + * type declaration: + * + *

              <K extends java.util.Collection<String> &
              +     * java.util.List<String>>
              + * + *

              + * since {@code List} is a subinterface of {@code Collection}, + * this method will return the bounds as if the declaration had been: + *

              + * + *
              <K extends java.util.List<String>>
              + * + * @param bounds an array of types representing the upper bounds of either + * {@link WildcardType} or {@link TypeVariable}, not {@code null}. + * @return an array containing the values from {@code bounds} minus the + * redundant types. + */ + public static Type[] normalizeUpperBounds(final Type[] bounds) { + Validate.notNull(bounds, "null value specified for bounds array"); + // don't bother if there's only one (or none) type + if (bounds.length < 2) { + return bounds; + } + + final Set types = new HashSet(bounds.length); + + for (final Type type1 : bounds) { + boolean subtypeFound = false; + + for (final Type type2 : bounds) { + if (type1 != type2 && isAssignable(type2, type1, null)) { + subtypeFound = true; + break; + } + } + + if (!subtypeFound) { + types.add(type1); + } + } + + return types.toArray(new Type[types.size()]); + } + + /** + *

              Returns an array containing the sole type of {@link Object} if + * {@link TypeVariable#getBounds()} returns an empty array. Otherwise, it + * returns the result of {@link TypeVariable#getBounds()} passed into + * {@link #normalizeUpperBounds}.

              + * + * @param typeVariable the subject type variable, not {@code null} + * @return a non-empty array containing the bounds of the type variable. + */ + public static Type[] getImplicitBounds(final TypeVariable typeVariable) { + Validate.notNull(typeVariable, "typeVariable is null"); + final Type[] bounds = typeVariable.getBounds(); + + return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds); + } + + /** + *

              Returns an array containing the sole value of {@link Object} if + * {@link WildcardType#getUpperBounds()} returns an empty array. Otherwise, + * it returns the result of {@link WildcardType#getUpperBounds()} + * passed into {@link #normalizeUpperBounds}.

              + * + * @param wildcardType the subject wildcard type, not {@code null} + * @return a non-empty array containing the upper bounds of the wildcard + * type. + */ + public static Type[] getImplicitUpperBounds(final WildcardType wildcardType) { + Validate.notNull(wildcardType, "wildcardType is null"); + final Type[] bounds = wildcardType.getUpperBounds(); + + return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds); + } + + /** + *

              Returns an array containing a single value of {@code null} if + * {@link WildcardType#getLowerBounds()} returns an empty array. Otherwise, + * it returns the result of {@link WildcardType#getLowerBounds()}.

              + * + * @param wildcardType the subject wildcard type, not {@code null} + * @return a non-empty array containing the lower bounds of the wildcard + * type. + */ + public static Type[] getImplicitLowerBounds(final WildcardType wildcardType) { + Validate.notNull(wildcardType, "wildcardType is null"); + final Type[] bounds = wildcardType.getLowerBounds(); + + return bounds.length == 0 ? new Type[] { null } : bounds; + } + + /** + *

              Determines whether or not specified types satisfy the bounds of their + * mapped type variables. When a type parameter extends another (such as + * {@code }), uses another as a type parameter (such as + * {@code >}), or otherwise depends on + * another type variable to be specified, the dependencies must be included + * in {@code typeVarAssigns}.

              + * + * @param typeVarAssigns specifies the potential types to be assigned to the + * type variables, not {@code null}. + * @return whether or not the types can be assigned to their respective type + * variables. + */ + public static boolean typesSatisfyVariables(final Map, Type> typeVarAssigns) { + Validate.notNull(typeVarAssigns, "typeVarAssigns is null"); + // all types must be assignable to all the bounds of the their mapped + // type variable. + for (final Map.Entry, Type> entry : typeVarAssigns.entrySet()) { + final TypeVariable typeVar = entry.getKey(); + final Type type = entry.getValue(); + + for (final Type bound : getImplicitBounds(typeVar)) { + if (!isAssignable(type, substituteTypeVariables(bound, typeVarAssigns), + typeVarAssigns)) { + return false; + } + } + } + return true; + } + + /** + *

              Transforms the passed in type to a {@link Class} object. Type-checking method of convenience.

              + * + * @param parameterizedType the type to be converted + * @return the corresponding {@code Class} object + * @throws IllegalStateException if the conversion fails + */ + private static Class getRawType(final ParameterizedType parameterizedType) { + final Type rawType = parameterizedType.getRawType(); + + // check if raw type is a Class object + // not currently necessary, but since the return type is Type instead of + // Class, there's enough reason to believe that future versions of Java + // may return other Type implementations. And type-safety checking is + // rarely a bad idea. + if (!(rawType instanceof Class)) { + throw new IllegalStateException("Wait... What!? Type of rawType: " + rawType); + } + + return (Class) rawType; + } + + /** + *

              Get the raw type of a Java type, given its context. Primarily for use + * with {@link TypeVariable}s and {@link GenericArrayType}s, or when you do + * not know the runtime type of {@code type}: if you know you have a + * {@link Class} instance, it is already raw; if you know you have a + * {@link ParameterizedType}, its raw type is only a method call away.

              + * + * @param type to resolve + * @param assigningType type to be resolved against + * @return the resolved {@link Class} object or {@code null} if + * the type could not be resolved + */ + public static Class getRawType(final Type type, final Type assigningType) { + if (type instanceof Class) { + // it is raw, no problem + return (Class) type; + } + + if (type instanceof ParameterizedType) { + // simple enough to get the raw type of a ParameterizedType + return getRawType((ParameterizedType) type); + } + + if (type instanceof TypeVariable) { + if (assigningType == null) { + return null; + } + + // get the entity declaring this type variable + final Object genericDeclaration = ((TypeVariable) type).getGenericDeclaration(); + + // can't get the raw type of a method- or constructor-declared type + // variable + if (!(genericDeclaration instanceof Class)) { + return null; + } + + // get the type arguments for the declaring class/interface based + // on the enclosing type + final Map, Type> typeVarAssigns = getTypeArguments(assigningType, + (Class) genericDeclaration); + + // enclosingType has to be a subclass (or subinterface) of the + // declaring type + if (typeVarAssigns == null) { + return null; + } + + // get the argument assigned to this type variable + final Type typeArgument = typeVarAssigns.get(type); + + if (typeArgument == null) { + return null; + } + + // get the argument for this type variable + return getRawType(typeArgument, assigningType); + } + + if (type instanceof GenericArrayType) { + // get raw component type + final Class rawComponentType = getRawType(((GenericArrayType) type) + .getGenericComponentType(), assigningType); + + // create array type from raw component type and return its class + return Array.newInstance(rawComponentType, 0).getClass(); + } + + // (hand-waving) this is not the method you're looking for + if (type instanceof WildcardType) { + return null; + } + + throw new IllegalArgumentException("unknown type: " + type); + } + + /** + * Learn whether the specified type denotes an array type. + * @param type the type to be checked + * @return {@code true} if {@code type} is an array class or a {@link GenericArrayType}. + */ + public static boolean isArrayType(final Type type) { + return type instanceof GenericArrayType || type instanceof Class && ((Class) type).isArray(); + } + + /** + * Get the array component type of {@code type}. + * @param type the type to be checked + * @return component type or null if type is not an array type + */ + public static Type getArrayComponentType(final Type type) { + if (type instanceof Class) { + final Class clazz = (Class) type; + return clazz.isArray() ? clazz.getComponentType() : null; + } + if (type instanceof GenericArrayType) { + return ((GenericArrayType) type).getGenericComponentType(); + } + return null; + } + + /** + * Get a type representing {@code type} with variable assignments "unrolled." + * + * @param typeArguments as from {@link TypeUtils#getTypeArguments(Type, Class)} + * @param type the type to unroll variable assignments for + * @return Type + * @since 3.2 + */ + public static Type unrollVariables(Map, Type> typeArguments, final Type type) { + if (typeArguments == null) { + typeArguments = Collections., Type> emptyMap(); + } + if (containsTypeVariables(type)) { + if (type instanceof TypeVariable) { + return unrollVariables(typeArguments, typeArguments.get(type)); + } + if (type instanceof ParameterizedType) { + final ParameterizedType p = (ParameterizedType) type; + final Map, Type> parameterizedTypeArguments; + if (p.getOwnerType() == null) { + parameterizedTypeArguments = typeArguments; + } else { + parameterizedTypeArguments = new HashMap, Type>(typeArguments); + parameterizedTypeArguments.putAll(TypeUtils.getTypeArguments(p)); + } + final Type[] args = p.getActualTypeArguments(); + for (int i = 0; i < args.length; i++) { + final Type unrolled = unrollVariables(parameterizedTypeArguments, args[i]); + if (unrolled != null) { + args[i] = unrolled; + } + } + return parameterizeWithOwner(p.getOwnerType(), (Class) p.getRawType(), args); + } + if (type instanceof WildcardType) { + final WildcardType wild = (WildcardType) type; + return wildcardType().withUpperBounds(unrollBounds(typeArguments, wild.getUpperBounds())) + .withLowerBounds(unrollBounds(typeArguments, wild.getLowerBounds())).build(); + } + } + return type; + } + + /** + * Local helper method to unroll variables in a type bounds array. + * + * @param typeArguments assignments {@link Map} + * @param bounds in which to expand variables + * @return {@code bounds} with any variables reassigned + * @since 3.2 + */ + private static Type[] unrollBounds(final Map, Type> typeArguments, final Type[] bounds) { + Type[] result = bounds; + int i = 0; + for (; i < result.length; i++) { + final Type unrolled = unrollVariables(typeArguments, result[i]); + if (unrolled == null) { + result = ArrayUtils.remove(result, i--); + } else { + result[i] = unrolled; + } + } + return result; + } + + /** + * Learn, recursively, whether any of the type parameters associated with {@code type} are bound to variables. + * + * @param type the type to check for type variables + * @return boolean + * @since 3.2 + */ + public static boolean containsTypeVariables(final Type type) { + if (type instanceof TypeVariable) { + return true; + } + if (type instanceof Class) { + return ((Class) type).getTypeParameters().length > 0; + } + if (type instanceof ParameterizedType) { + for (final Type arg : ((ParameterizedType) type).getActualTypeArguments()) { + if (containsTypeVariables(arg)) { + return true; + } + } + return false; + } + if (type instanceof WildcardType) { + final WildcardType wild = (WildcardType) type; + return containsTypeVariables(TypeUtils.getImplicitLowerBounds(wild)[0]) + || containsTypeVariables(TypeUtils.getImplicitUpperBounds(wild)[0]); + } + return false; + } + + /** + * Create a parameterized type instance. + * + * @param raw the raw class to create a parameterized type instance for + * @param typeArguments the types used for parameterization + * @return {@link ParameterizedType} + * @since 3.2 + */ + public static final ParameterizedType parameterize(final Class raw, final Type... typeArguments) { + return parameterizeWithOwner(null, raw, typeArguments); + } + + /** + * Create a parameterized type instance. + * + * @param raw the raw class to create a parameterized type instance for + * @param typeArgMappings the mapping used for parameterization + * @return {@link ParameterizedType} + * @since 3.2 + */ + public static final ParameterizedType parameterize(final Class raw, + final Map, Type> typeArgMappings) { + Validate.notNull(raw, "raw class is null"); + Validate.notNull(typeArgMappings, "typeArgMappings is null"); + return parameterizeWithOwner(null, raw, extractTypeArgumentsFrom(typeArgMappings, raw.getTypeParameters())); + } + + /** + * Create a parameterized type instance. + * + * @param owner the owning type + * @param raw the raw class to create a parameterized type instance for + * @param typeArguments the types used for parameterization + * + * @return {@link ParameterizedType} + * @since 3.2 + */ + public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class raw, + final Type... typeArguments) { + Validate.notNull(raw, "raw class is null"); + final Type useOwner; + if (raw.getEnclosingClass() == null) { + Validate.isTrue(owner == null, "no owner allowed for top-level %s", raw); + useOwner = null; + } else if (owner == null) { + useOwner = raw.getEnclosingClass(); + } else { + Validate.isTrue(TypeUtils.isAssignable(owner, raw.getEnclosingClass()), + "%s is invalid owner type for parameterized %s", owner, raw); + useOwner = owner; + } + Validate.noNullElements(typeArguments, "null type argument at index %s"); + Validate.isTrue(raw.getTypeParameters().length == typeArguments.length, + "invalid number of type parameters specified: expected %s, got %s", raw.getTypeParameters().length, + typeArguments.length); + + return new ParameterizedTypeImpl(raw, useOwner, typeArguments); + } + + /** + * Create a parameterized type instance. + * + * @param owner the owning type + * @param raw the raw class to create a parameterized type instance for + * @param typeArgMappings the mapping used for parameterization + * @return {@link ParameterizedType} + * @since 3.2 + */ + public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class raw, + final Map, Type> typeArgMappings) { + Validate.notNull(raw, "raw class is null"); + Validate.notNull(typeArgMappings, "typeArgMappings is null"); + return parameterizeWithOwner(owner, raw, extractTypeArgumentsFrom(typeArgMappings, raw.getTypeParameters())); + } + + /** + * Helper method to establish the formal parameters for a parameterized type. + * @param mappings map containing the assignements + * @param variables expected map keys + * @return array of map values corresponding to specified keys + */ + private static Type[] extractTypeArgumentsFrom(final Map, Type> mappings, final TypeVariable[] variables) { + final Type[] result = new Type[variables.length]; + int index = 0; + for (final TypeVariable var : variables) { + Validate.isTrue(mappings.containsKey(var), "missing argument mapping for %s", toString(var)); + result[index++] = mappings.get(var); + } + return result; + } + + /** + * Get a {@link WildcardTypeBuilder}. + * @return {@link WildcardTypeBuilder} + * @since 3.2 + */ + public static WildcardTypeBuilder wildcardType() { + return new WildcardTypeBuilder(); + } + + /** + * Create a generic array type instance. + * + * @param componentType the type of the elements of the array. For example the component type of {@code boolean[]} + * is {@code boolean} + * @return {@link GenericArrayType} + * @since 3.2 + */ + public static GenericArrayType genericArrayType(final Type componentType) { + return new GenericArrayTypeImpl(Validate.notNull(componentType, "componentType is null")); + } + + /** + * Check equality of types. + * + * @param t1 the first type + * @param t2 the second type + * @return boolean + * @since 3.2 + */ + @SuppressWarnings( "deprecation" ) // ObjectUtils.equals(Object, Object) has been deprecated in 3.2 + public static boolean equals(final Type t1, final Type t2) { + if (ObjectUtils.equals(t1, t2)) { + return true; + } + if (t1 instanceof ParameterizedType) { + return equals((ParameterizedType) t1, t2); + } + if (t1 instanceof GenericArrayType) { + return equals((GenericArrayType) t1, t2); + } + if (t1 instanceof WildcardType) { + return equals((WildcardType) t1, t2); + } + return false; + } + + /** + * Learn whether {@code t} equals {@code p}. + * @param p LHS + * @param t RHS + * @return boolean + * @since 3.2 + */ + private static boolean equals(final ParameterizedType p, final Type t) { + if (t instanceof ParameterizedType) { + final ParameterizedType other = (ParameterizedType) t; + if (equals(p.getRawType(), other.getRawType()) && equals(p.getOwnerType(), other.getOwnerType())) { + return equals(p.getActualTypeArguments(), other.getActualTypeArguments()); + } + } + return false; + } + + /** + * Learn whether {@code t} equals {@code a}. + * @param a LHS + * @param t RHS + * @return boolean + * @since 3.2 + */ + private static boolean equals(final GenericArrayType a, final Type t) { + return t instanceof GenericArrayType + && equals(a.getGenericComponentType(), ((GenericArrayType) t).getGenericComponentType()); + } + + /** + * Learn whether {@code t} equals {@code w}. + * @param w LHS + * @param t RHS + * @return boolean + * @since 3.2 + */ + private static boolean equals(final WildcardType w, final Type t) { + if (t instanceof WildcardType) { + final WildcardType other = (WildcardType) t; + return equals(getImplicitLowerBounds(w), getImplicitLowerBounds(other)) + && equals(getImplicitUpperBounds(w), getImplicitUpperBounds(other)); + } + return true; + } + + /** + * Learn whether {@code t1} equals {@code t2}. + * @param t1 LHS + * @param t2 RHS + * @return boolean + * @since 3.2 + */ + private static boolean equals(final Type[] t1, final Type[] t2) { + if (t1.length == t2.length) { + for (int i = 0; i < t1.length; i++) { + if (!equals(t1[i], t2[i])) { + return false; + } + } + return true; + } + return false; + } + + /** + * Present a given type as a Java-esque String. + * + * @param type the type to create a String representation for, not {@code null} + * @return String + * @since 3.2 + */ + public static String toString(final Type type) { + Validate.notNull(type); + if (type instanceof Class) { + return classToString((Class) type); + } + if (type instanceof ParameterizedType) { + return parameterizedTypeToString((ParameterizedType) type); + } + if (type instanceof WildcardType) { + return wildcardTypeToString((WildcardType) type); + } + if (type instanceof TypeVariable) { + return typeVariableToString((TypeVariable) type); + } + if (type instanceof GenericArrayType) { + return genericArrayTypeToString((GenericArrayType) type); + } + throw new IllegalArgumentException(ObjectUtils.identityToString(type)); + } + + /** + * Format a {@link TypeVariable} including its {@link GenericDeclaration}. + * + * @param var the type variable to create a String representation for, not {@code null} + * @return String + * @since 3.2 + */ + public static String toLongString(final TypeVariable var) { + Validate.notNull(var, "var is null"); + final StringBuilder buf = new StringBuilder(); + final GenericDeclaration d = ((TypeVariable) var).getGenericDeclaration(); + if (d instanceof Class) { + Class c = (Class) d; + while (true) { + if (c.getEnclosingClass() == null) { + buf.insert(0, c.getName()); + break; + } + buf.insert(0, c.getSimpleName()).insert(0, '.'); + c = c.getEnclosingClass(); + } + } else if (d instanceof Type) {// not possible as of now + buf.append(toString((Type) d)); + } else { + buf.append(d); + } + return buf.append(':').append(typeVariableToString(var)).toString(); + } + + /** + * Wrap the specified {@link Type} in a {@link Typed} wrapper. + * + * @param inferred generic type + * @param type to wrap + * @return Typed<T> + * @since 3.2 + */ + public static Typed wrap(final Type type) { + return new Typed() { + @Override + public Type getType() { + return type; + } + }; + } + + /** + * Wrap the specified {@link Class} in a {@link Typed} wrapper. + * + * @param generic type + * @param type to wrap + * @return Typed<T> + * @since 3.2 + */ + public static Typed wrap(final Class type) { + return TypeUtils. wrap((Type) type); + } + + /** + * Format a {@link Class} as a {@link String}. + * @param c {@code Class} to format + * @return String + * @since 3.2 + */ + private static String classToString(final Class c) { + final StringBuilder buf = new StringBuilder(); + + if (c.getEnclosingClass() != null) { + buf.append(classToString(c.getEnclosingClass())).append('.').append(c.getSimpleName()); + } else { + buf.append(c.getName()); + } + if (c.getTypeParameters().length > 0) { + buf.append('<'); + appendAllTo(buf, ", ", c.getTypeParameters()); + buf.append('>'); + } + return buf.toString(); + } + + /** + * Format a {@link TypeVariable} as a {@link String}. + * @param v {@code TypeVariable} to format + * @return String + * @since 3.2 + */ + private static String typeVariableToString(final TypeVariable v) { + final StringBuilder buf = new StringBuilder(v.getName()); + final Type[] bounds = v.getBounds(); + if (bounds.length > 0 && !(bounds.length == 1 && Object.class.equals(bounds[0]))) { + buf.append(" extends "); + appendAllTo(buf, " & ", v.getBounds()); + } + return buf.toString(); + } + + /** + * Format a {@link ParameterizedType} as a {@link String}. + * @param p {@code ParameterizedType} to format + * @return String + * @since 3.2 + */ + private static String parameterizedTypeToString(final ParameterizedType p) { + final StringBuilder buf = new StringBuilder(); + + final Type useOwner = p.getOwnerType(); + final Class raw = (Class) p.getRawType(); + final Type[] typeArguments = p.getActualTypeArguments(); + if (useOwner == null) { + buf.append(raw.getName()); + } else { + if (useOwner instanceof Class) { + buf.append(((Class) useOwner).getName()); + } else { + buf.append(useOwner.toString()); + } + buf.append('.').append(raw.getSimpleName()); + } + + appendAllTo(buf.append('<'), ", ", typeArguments).append('>'); + return buf.toString(); + } + + /** + * Format a {@link WildcardType} as a {@link String}. + * @param w {@code WildcardType} to format + * @return String + * @since 3.2 + */ + private static String wildcardTypeToString(final WildcardType w) { + final StringBuilder buf = new StringBuilder().append('?'); + final Type[] lowerBounds = w.getLowerBounds(); + final Type[] upperBounds = w.getUpperBounds(); + if (lowerBounds.length > 1 || lowerBounds.length == 1 && lowerBounds[0] != null) { + appendAllTo(buf.append(" super "), " & ", lowerBounds); + } else if (upperBounds.length > 1 || upperBounds.length == 1 && !Object.class.equals(upperBounds[0])) { + appendAllTo(buf.append(" extends "), " & ", upperBounds); + } + return buf.toString(); + } + + /** + * Format a {@link GenericArrayType} as a {@link String}. + * @param g {@code GenericArrayType} to format + * @return String + * @since 3.2 + */ + private static String genericArrayTypeToString(final GenericArrayType g) { + return String.format("%s[]", toString(g.getGenericComponentType())); + } + + /** + * Append {@code types} to @{code buf} with separator {@code sep}. + * @param buf destination + * @param sep separator + * @param types to append + * @return {@code buf} + * @since 3.2 + */ + private static StringBuilder appendAllTo(final StringBuilder buf, final String sep, final Type... types) { + Validate.notEmpty(Validate.noNullElements(types)); + if (types.length > 0) { + buf.append(toString(types[0])); + for (int i = 1; i < types.length; i++) { + buf.append(sep).append(toString(types[i])); + } + } + return buf; + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/Typed.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/Typed.java new file mode 100644 index 000000000..78c1139f2 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/Typed.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.reflect; + +import java.lang.reflect.Type; + +/** + * Generalization of "has a type." + * @see TypeLiteral + * @since 3.2 + * @version $Id: Typed.java 1552659 2013-12-20 13:35:22Z britter $ + */ +public interface Typed { + + /** + * Get the {@link Type} represented by this entity. + * + * @return Type + */ + Type getType(); +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/package-info.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/package-info.java new file mode 100644 index 000000000..efa3be745 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/reflect/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *

              Accumulates common high-level uses of the {@code java.lang.reflect} APIs.

              + *

              These classes are immutable, and therefore thread-safe.

              + * + * @since 3.0 + * @version $Id: package-info.java 1558546 2014-01-15 19:38:15Z britter $ + */ +package com.fr.third.org.apache.commons.lang3.reflect; diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/CompositeFormat.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/CompositeFormat.java new file mode 100644 index 000000000..da7b8ca09 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/CompositeFormat.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text; + +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParseException; +import java.text.ParsePosition; + +/** + * Formats using one formatter and parses using a different formatter. An + * example of use for this would be a webapp where data is taken in one way and + * stored in a database another way. + * + * @version $Id: CompositeFormat.java 1436768 2013-01-22 07:07:42Z ggregory $ + */ +public class CompositeFormat extends Format { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = -4329119827877627683L; + + /** The parser to use. */ + private final Format parser; + /** The formatter to use. */ + private final Format formatter; + + /** + * Create a format that points its parseObject method to one implementation + * and its format method to another. + * + * @param parser implementation + * @param formatter implementation + */ + public CompositeFormat(final Format parser, final Format formatter) { + this.parser = parser; + this.formatter = formatter; + } + + /** + * Uses the formatter Format instance. + * + * @param obj the object to format + * @param toAppendTo the {@link StringBuffer} to append to + * @param pos the FieldPosition to use (or ignore). + * @return toAppendTo + * @see Format#format(Object, StringBuffer, FieldPosition) + */ + @Override // Therefore has to use StringBuffer + public StringBuffer format(final Object obj, final StringBuffer toAppendTo, + final FieldPosition pos) { + return formatter.format(obj, toAppendTo, pos); + } + + /** + * Uses the parser Format instance. + * + * @param source the String source + * @param pos the ParsePosition containing the position to parse from, will + * be updated according to parsing success (index) or failure + * (error index) + * @return the parsed Object + * @see Format#parseObject(String, ParsePosition) + */ + @Override + public Object parseObject(final String source, final ParsePosition pos) { + return parser.parseObject(source, pos); + } + + /** + * Provides access to the parser Format implementation. + * + * @return parser Format implementation + */ + public Format getParser() { + return this.parser; + } + + /** + * Provides access to the parser Format implementation. + * + * @return formatter Format implementation + */ + public Format getFormatter() { + return this.formatter; + } + + /** + * Utility method to parse and then reformat a String. + * + * @param input String to reformat + * @return A reformatted String + * @throws ParseException thrown by parseObject(String) call + */ + public String reformat(final String input) throws ParseException { + return format(parseObject(input)); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/ExtendedMessageFormat.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/ExtendedMessageFormat.java new file mode 100644 index 000000000..bddb7180b --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/ExtendedMessageFormat.java @@ -0,0 +1,529 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text; + +import java.text.Format; +import java.text.MessageFormat; +import java.text.ParsePosition; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import com.fr.third.org.apache.commons.lang3.ObjectUtils; +import com.fr.third.org.apache.commons.lang3.Validate; + +/** + * Extends java.text.MessageFormat to allow pluggable/additional formatting + * options for embedded format elements. Client code should specify a registry + * of FormatFactory instances associated with String + * format names. This registry will be consulted when the format elements are + * parsed from the message pattern. In this way custom patterns can be specified, + * and the formats supported by java.text.MessageFormat can be overridden + * at the format and/or format style level (see MessageFormat). A "format element" + * embedded in the message pattern is specified (()? signifies optionality):
              + * {argument-number(,format-name + * (,format-style)?)?} + * + *

              + * format-name and format-style values are trimmed of surrounding whitespace + * in the manner of java.text.MessageFormat. If format-name denotes + * FormatFactory formatFactoryInstance in registry, a Format + * matching format-name and format-style is requested from + * formatFactoryInstance. If this is successful, the Format + * found is used for this format element. + *

              + * + *

              NOTICE: The various subformat mutator methods are considered unnecessary; they exist on the parent + * class to allow the type of customization which it is the job of this class to provide in + * a configurable fashion. These methods have thus been disabled and will throw + * UnsupportedOperationException if called. + *

              + * + *

              Limitations inherited from java.text.MessageFormat:

              + *
                + *
              • When using "choice" subformats, support for nested formatting instructions is limited + * to that provided by the base class.
              • + *
              • Thread-safety of Formats, including MessageFormat and thus + * ExtendedMessageFormat, is not guaranteed.
              • + *
              + * + * @since 2.4 + * @version $Id: ExtendedMessageFormat.java 1669787 2015-03-28 15:12:47Z britter $ + */ +public class ExtendedMessageFormat extends MessageFormat { + private static final long serialVersionUID = -2362048321261811743L; + private static final int HASH_SEED = 31; + + private static final String DUMMY_PATTERN = ""; + private static final char START_FMT = ','; + private static final char END_FE = '}'; + private static final char START_FE = '{'; + private static final char QUOTE = '\''; + + private String toPattern; + private final Map registry; + + /** + * Create a new ExtendedMessageFormat for the default locale. + * + * @param pattern the pattern to use, not null + * @throws IllegalArgumentException in case of a bad pattern. + */ + public ExtendedMessageFormat(final String pattern) { + this(pattern, Locale.getDefault()); + } + + /** + * Create a new ExtendedMessageFormat. + * + * @param pattern the pattern to use, not null + * @param locale the locale to use, not null + * @throws IllegalArgumentException in case of a bad pattern. + */ + public ExtendedMessageFormat(final String pattern, final Locale locale) { + this(pattern, locale, null); + } + + /** + * Create a new ExtendedMessageFormat for the default locale. + * + * @param pattern the pattern to use, not null + * @param registry the registry of format factories, may be null + * @throws IllegalArgumentException in case of a bad pattern. + */ + public ExtendedMessageFormat(final String pattern, final Map registry) { + this(pattern, Locale.getDefault(), registry); + } + + /** + * Create a new ExtendedMessageFormat. + * + * @param pattern the pattern to use, not null + * @param locale the locale to use, not null + * @param registry the registry of format factories, may be null + * @throws IllegalArgumentException in case of a bad pattern. + */ + public ExtendedMessageFormat(final String pattern, final Locale locale, final Map registry) { + super(DUMMY_PATTERN); + setLocale(locale); + this.registry = registry; + applyPattern(pattern); + } + + /** + * {@inheritDoc} + */ + @Override + public String toPattern() { + return toPattern; + } + + /** + * Apply the specified pattern. + * + * @param pattern String + */ + @Override + public final void applyPattern(final String pattern) { + if (registry == null) { + super.applyPattern(pattern); + toPattern = super.toPattern(); + return; + } + final ArrayList foundFormats = new ArrayList(); + final ArrayList foundDescriptions = new ArrayList(); + final StringBuilder stripCustom = new StringBuilder(pattern.length()); + + final ParsePosition pos = new ParsePosition(0); + final char[] c = pattern.toCharArray(); + int fmtCount = 0; + while (pos.getIndex() < pattern.length()) { + switch (c[pos.getIndex()]) { + case QUOTE: + appendQuotedString(pattern, pos, stripCustom); + break; + case START_FE: + fmtCount++; + seekNonWs(pattern, pos); + final int start = pos.getIndex(); + final int index = readArgumentIndex(pattern, next(pos)); + stripCustom.append(START_FE).append(index); + seekNonWs(pattern, pos); + Format format = null; + String formatDescription = null; + if (c[pos.getIndex()] == START_FMT) { + formatDescription = parseFormatDescription(pattern, + next(pos)); + format = getFormat(formatDescription); + if (format == null) { + stripCustom.append(START_FMT).append(formatDescription); + } + } + foundFormats.add(format); + foundDescriptions.add(format == null ? null : formatDescription); + Validate.isTrue(foundFormats.size() == fmtCount); + Validate.isTrue(foundDescriptions.size() == fmtCount); + if (c[pos.getIndex()] != END_FE) { + throw new IllegalArgumentException( + "Unreadable format element at position " + start); + } + //$FALL-THROUGH$ + default: + stripCustom.append(c[pos.getIndex()]); + next(pos); + } + } + super.applyPattern(stripCustom.toString()); + toPattern = insertFormats(super.toPattern(), foundDescriptions); + if (containsElements(foundFormats)) { + final Format[] origFormats = getFormats(); + // only loop over what we know we have, as MessageFormat on Java 1.3 + // seems to provide an extra format element: + int i = 0; + for (final Iterator it = foundFormats.iterator(); it.hasNext(); i++) { + final Format f = it.next(); + if (f != null) { + origFormats[i] = f; + } + } + super.setFormats(origFormats); + } + } + + /** + * Throws UnsupportedOperationException - see class Javadoc for details. + * + * @param formatElementIndex format element index + * @param newFormat the new format + * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat + */ + @Override + public void setFormat(final int formatElementIndex, final Format newFormat) { + throw new UnsupportedOperationException(); + } + + /** + * Throws UnsupportedOperationException - see class Javadoc for details. + * + * @param argumentIndex argument index + * @param newFormat the new format + * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat + */ + @Override + public void setFormatByArgumentIndex(final int argumentIndex, final Format newFormat) { + throw new UnsupportedOperationException(); + } + + /** + * Throws UnsupportedOperationException - see class Javadoc for details. + * + * @param newFormats new formats + * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat + */ + @Override + public void setFormats(final Format[] newFormats) { + throw new UnsupportedOperationException(); + } + + /** + * Throws UnsupportedOperationException - see class Javadoc for details. + * + * @param newFormats new formats + * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat + */ + @Override + public void setFormatsByArgumentIndex(final Format[] newFormats) { + throw new UnsupportedOperationException(); + } + + /** + * Check if this extended message format is equal to another object. + * + * @param obj the object to compare to + * @return true if this object equals the other, otherwise false + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj == null) { + return false; + } + if (!super.equals(obj)) { + return false; + } + if (ObjectUtils.notEqual(getClass(), obj.getClass())) { + return false; + } + final ExtendedMessageFormat rhs = (ExtendedMessageFormat)obj; + if (ObjectUtils.notEqual(toPattern, rhs.toPattern)) { + return false; + } + if (ObjectUtils.notEqual(registry, rhs.registry)) { + return false; + } + return true; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings( "deprecation" ) // ObjectUtils.hashCode(Object) has been deprecated in 3.2 + @Override + public int hashCode() { + int result = super.hashCode(); + result = HASH_SEED * result + ObjectUtils.hashCode(registry); + result = HASH_SEED * result + ObjectUtils.hashCode(toPattern); + return result; + } + + /** + * Get a custom format from a format description. + * + * @param desc String + * @return Format + */ + private Format getFormat(final String desc) { + if (registry != null) { + String name = desc; + String args = null; + final int i = desc.indexOf(START_FMT); + if (i > 0) { + name = desc.substring(0, i).trim(); + args = desc.substring(i + 1).trim(); + } + final FormatFactory factory = registry.get(name); + if (factory != null) { + return factory.getFormat(name, args, getLocale()); + } + } + return null; + } + + /** + * Read the argument index from the current format element + * + * @param pattern pattern to parse + * @param pos current parse position + * @return argument index + */ + private int readArgumentIndex(final String pattern, final ParsePosition pos) { + final int start = pos.getIndex(); + seekNonWs(pattern, pos); + final StringBuilder result = new StringBuilder(); + boolean error = false; + for (; !error && pos.getIndex() < pattern.length(); next(pos)) { + char c = pattern.charAt(pos.getIndex()); + if (Character.isWhitespace(c)) { + seekNonWs(pattern, pos); + c = pattern.charAt(pos.getIndex()); + if (c != START_FMT && c != END_FE) { + error = true; + continue; + } + } + if ((c == START_FMT || c == END_FE) && result.length() > 0) { + try { + return Integer.parseInt(result.toString()); + } catch (final NumberFormatException e) { // NOPMD + // we've already ensured only digits, so unless something + // outlandishly large was specified we should be okay. + } + } + error = !Character.isDigit(c); + result.append(c); + } + if (error) { + throw new IllegalArgumentException( + "Invalid format argument index at position " + start + ": " + + pattern.substring(start, pos.getIndex())); + } + throw new IllegalArgumentException( + "Unterminated format element at position " + start); + } + + /** + * Parse the format component of a format element. + * + * @param pattern string to parse + * @param pos current parse position + * @return Format description String + */ + private String parseFormatDescription(final String pattern, final ParsePosition pos) { + final int start = pos.getIndex(); + seekNonWs(pattern, pos); + final int text = pos.getIndex(); + int depth = 1; + for (; pos.getIndex() < pattern.length(); next(pos)) { + switch (pattern.charAt(pos.getIndex())) { + case START_FE: + depth++; + break; + case END_FE: + depth--; + if (depth == 0) { + return pattern.substring(text, pos.getIndex()); + } + break; + case QUOTE: + getQuotedString(pattern, pos); + break; + default: + break; + } + } + throw new IllegalArgumentException( + "Unterminated format element at position " + start); + } + + /** + * Insert formats back into the pattern for toPattern() support. + * + * @param pattern source + * @param customPatterns The custom patterns to re-insert, if any + * @return full pattern + */ + private String insertFormats(final String pattern, final ArrayList customPatterns) { + if (!containsElements(customPatterns)) { + return pattern; + } + final StringBuilder sb = new StringBuilder(pattern.length() * 2); + final ParsePosition pos = new ParsePosition(0); + int fe = -1; + int depth = 0; + while (pos.getIndex() < pattern.length()) { + final char c = pattern.charAt(pos.getIndex()); + switch (c) { + case QUOTE: + appendQuotedString(pattern, pos, sb); + break; + case START_FE: + depth++; + sb.append(START_FE).append(readArgumentIndex(pattern, next(pos))); + // do not look for custom patterns when they are embedded, e.g. in a choice + if (depth == 1) { + fe++; + final String customPattern = customPatterns.get(fe); + if (customPattern != null) { + sb.append(START_FMT).append(customPattern); + } + } + break; + case END_FE: + depth--; + //$FALL-THROUGH$ + default: + sb.append(c); + next(pos); + } + } + return sb.toString(); + } + + /** + * Consume whitespace from the current parse position. + * + * @param pattern String to read + * @param pos current position + */ + private void seekNonWs(final String pattern, final ParsePosition pos) { + int len = 0; + final char[] buffer = pattern.toCharArray(); + do { + len = StrMatcher.splitMatcher().isMatch(buffer, pos.getIndex()); + pos.setIndex(pos.getIndex() + len); + } while (len > 0 && pos.getIndex() < pattern.length()); + } + + /** + * Convenience method to advance parse position by 1 + * + * @param pos ParsePosition + * @return pos + */ + private ParsePosition next(final ParsePosition pos) { + pos.setIndex(pos.getIndex() + 1); + return pos; + } + + /** + * Consume a quoted string, adding it to appendTo if + * specified. + * + * @param pattern pattern to parse + * @param pos current parse position + * @param appendTo optional StringBuilder to append + * @return appendTo + */ + private StringBuilder appendQuotedString(final String pattern, final ParsePosition pos, + final StringBuilder appendTo) { + assert pattern.toCharArray()[pos.getIndex()] == QUOTE : + "Quoted string must start with quote character"; + + // handle quote character at the beginning of the string + if(appendTo != null) { + appendTo.append(QUOTE); + } + next(pos); + + final int start = pos.getIndex(); + final char[] c = pattern.toCharArray(); + int lastHold = start; + for (int i = pos.getIndex(); i < pattern.length(); i++) { + switch (c[pos.getIndex()]) { + case QUOTE: + next(pos); + return appendTo == null ? null : appendTo.append(c, lastHold, + pos.getIndex() - lastHold); + default: + next(pos); + } + } + throw new IllegalArgumentException( + "Unterminated quoted string at position " + start); + } + + /** + * Consume quoted string only + * + * @param pattern pattern to parse + * @param pos current parse position + */ + private void getQuotedString(final String pattern, final ParsePosition pos) { + appendQuotedString(pattern, pos, null); + } + + /** + * Learn whether the specified Collection contains non-null elements. + * @param coll to check + * @return true if some Object was found, false otherwise. + */ + private boolean containsElements(final Collection coll) { + if (coll == null || coll.isEmpty()) { + return false; + } + for (final Object name : coll) { + if (name != null) { + return true; + } + } + return false; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/FormatFactory.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/FormatFactory.java new file mode 100644 index 000000000..a021074fb --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/FormatFactory.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text; + +import java.text.Format; +import java.util.Locale; + +/** + * Format factory. + * + * @since 2.4 + * @version $Id: FormatFactory.java 1088899 2011-04-05 05:31:27Z bayard $ + */ +public interface FormatFactory { + + /** + * Create or retrieve a format instance. + * + * @param name The format type name + * @param arguments Arguments used to create the format instance. This allows the + * FormatFactory to implement the "format style" + * concept from java.text.MessageFormat. + * @param locale The locale, may be null + * @return The format instance + */ + Format getFormat(String name, String arguments, Locale locale); + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/FormattableUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/FormattableUtils.java new file mode 100644 index 000000000..e4b37440d --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/FormattableUtils.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text; + +import static java.util.FormattableFlags.LEFT_JUSTIFY; + +import java.util.Formattable; +import java.util.Formatter; + +import com.fr.third.org.apache.commons.lang3.StringUtils; +import com.fr.third.org.apache.commons.lang3.Validate; +import com.fr.third.org.apache.commons.lang3.ObjectUtils; + +/** + *

              Provides utilities for working with the {@code Formattable} interface.

              + * + *

              The {@link Formattable} interface provides basic control over formatting + * when using a {@code Formatter}. It is primarily concerned with numeric precision + * and padding, and is not designed to allow generalised alternate formats.

              + * + * @since Lang 3.0 + * @version $Id: FormattableUtils.java 1436770 2013-01-22 07:09:45Z ggregory $ + */ +public class FormattableUtils { + + /** + * A format that simply outputs the value as a string. + */ + private static final String SIMPLEST_FORMAT = "%s"; + + /** + *

              {@code FormattableUtils} instances should NOT be constructed in + * standard programming. Instead, the methods of the class should be invoked + * statically.

              + * + *

              This constructor is public to permit tools that require a JavaBean + * instance to operate.

              + */ + public FormattableUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + * Get the default formatted representation of the specified + * {@code Formattable}. + * + * @param formattable the instance to convert to a string, not null + * @return the resulting string, not null + */ + public static String toString(final Formattable formattable) { + return String.format(SIMPLEST_FORMAT, formattable); + } + + /** + * Handles the common {@code Formattable} operations of truncate-pad-append, + * with no ellipsis on precision overflow, and padding width underflow with + * spaces. + * + * @param seq the string to handle, not null + * @param formatter the destination formatter, not null + * @param flags the flags for formatting, see {@code Formattable} + * @param width the width of the output, see {@code Formattable} + * @param precision the precision of the output, see {@code Formattable} + * @return the {@code formatter} instance, not null + */ + public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width, + final int precision) { + return append(seq, formatter, flags, width, precision, ' ', null); + } + + /** + * Handles the common {@link Formattable} operations of truncate-pad-append, + * with no ellipsis on precision overflow. + * + * @param seq the string to handle, not null + * @param formatter the destination formatter, not null + * @param flags the flags for formatting, see {@code Formattable} + * @param width the width of the output, see {@code Formattable} + * @param precision the precision of the output, see {@code Formattable} + * @param padChar the pad character to use + * @return the {@code formatter} instance, not null + */ + public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width, + final int precision, final char padChar) { + return append(seq, formatter, flags, width, precision, padChar, null); + } + + /** + * Handles the common {@link Formattable} operations of truncate-pad-append, + * padding width underflow with spaces. + * + * @param seq the string to handle, not null + * @param formatter the destination formatter, not null + * @param flags the flags for formatting, see {@code Formattable} + * @param width the width of the output, see {@code Formattable} + * @param precision the precision of the output, see {@code Formattable} + * @param ellipsis the ellipsis to use when precision dictates truncation, null or + * empty causes a hard truncation + * @return the {@code formatter} instance, not null + */ + public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width, + final int precision, final CharSequence ellipsis) { + return append(seq, formatter, flags, width, precision, ' ', ellipsis); + } + + /** + * Handles the common {@link Formattable} operations of truncate-pad-append. + * + * @param seq the string to handle, not null + * @param formatter the destination formatter, not null + * @param flags the flags for formatting, see {@code Formattable} + * @param width the width of the output, see {@code Formattable} + * @param precision the precision of the output, see {@code Formattable} + * @param padChar the pad character to use + * @param ellipsis the ellipsis to use when precision dictates truncation, null or + * empty causes a hard truncation + * @return the {@code formatter} instance, not null + */ + public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width, + final int precision, final char padChar, final CharSequence ellipsis) { + Validate.isTrue(ellipsis == null || precision < 0 || ellipsis.length() <= precision, + "Specified ellipsis '%1$s' exceeds precision of %2$s", ellipsis, Integer.valueOf(precision)); + final StringBuilder buf = new StringBuilder(seq); + if (precision >= 0 && precision < seq.length()) { + final CharSequence _ellipsis = ObjectUtils.defaultIfNull(ellipsis, StringUtils.EMPTY); + buf.replace(precision - _ellipsis.length(), seq.length(), _ellipsis.toString()); + } + final boolean leftJustify = (flags & LEFT_JUSTIFY) == LEFT_JUSTIFY; + for (int i = buf.length(); i < width; i++) { + buf.insert(leftJustify ? i : 0, padChar); + } + formatter.format(buf.toString()); + return formatter; + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrBuilder.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrBuilder.java new file mode 100644 index 000000000..b97718f88 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrBuilder.java @@ -0,0 +1,3127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text; + +import java.io.IOException; +import java.io.Reader; +import java.io.Serializable; +import java.io.Writer; +import java.nio.CharBuffer; +import java.util.Iterator; +import java.util.List; + +import com.fr.third.org.apache.commons.lang3.builder.Builder; +import com.fr.third.org.apache.commons.lang3.ArrayUtils; +import com.fr.third.org.apache.commons.lang3.ObjectUtils; +import com.fr.third.org.apache.commons.lang3.SystemUtils; + +/** + * Builds a string from constituent parts providing a more flexible and powerful API + * than StringBuffer. + *

              + * The main differences from StringBuffer/StringBuilder are: + *

              + *
                + *
              • Not synchronized
              • + *
              • Not final
              • + *
              • Subclasses have direct access to character array
              • + *
              • Additional methods + *
                  + *
                • appendWithSeparators - adds an array of values, with a separator
                • + *
                • appendPadding - adds a length padding characters
                • + *
                • appendFixedLength - adds a fixed width field to the builder
                • + *
                • toCharArray/getChars - simpler ways to get a range of the character array
                • + *
                • delete - delete char or string
                • + *
                • replace - search and replace for a char or string
                • + *
                • leftString/rightString/midString - substring without exceptions
                • + *
                • contains - whether the builder contains a char or string
                • + *
                • size/clear/isEmpty - collections style API methods
                • + *
                + *
              • + *
              • Views + *
                  + *
                • asTokenizer - uses the internal buffer as the source of a StrTokenizer
                • + *
                • asReader - uses the internal buffer as the source of a Reader
                • + *
                • asWriter - allows a Writer to write directly to the internal buffer
                • + *
                + *
              • + *
              + *

              + * The aim has been to provide an API that mimics very closely what StringBuffer + * provides, but with additional methods. It should be noted that some edge cases, + * with invalid indices or null input, have been altered - see individual methods. + * The biggest of these changes is that by default, null will not output the text + * 'null'. This can be controlled by a property, {@link #setNullText(String)}. + *

              + * Prior to 3.0, this class implemented Cloneable but did not implement the + * clone method so could not be used. From 3.0 onwards it no longer implements + * the interface. + * + * @since 2.2 + * @version $Id: StrBuilder.java 1666669 2015-03-14 12:25:06Z britter $ + */ +public class StrBuilder implements CharSequence, Appendable, Serializable, Builder { + + /** + * The extra capacity for new builders. + */ + static final int CAPACITY = 32; + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 7628716375283629643L; + + /** Internal data storage. */ + protected char[] buffer; // TODO make private? + /** Current size of the buffer. */ + protected int size; // TODO make private? + /** The new line. */ + private String newLine; + /** The null text. */ + private String nullText; + + //----------------------------------------------------------------------- + /** + * Constructor that creates an empty builder initial capacity 32 characters. + */ + public StrBuilder() { + this(CAPACITY); + } + + /** + * Constructor that creates an empty builder the specified initial capacity. + * + * @param initialCapacity the initial capacity, zero or less will be converted to 32 + */ + public StrBuilder(int initialCapacity) { + super(); + if (initialCapacity <= 0) { + initialCapacity = CAPACITY; + } + buffer = new char[initialCapacity]; + } + + /** + * Constructor that creates a builder from the string, allocating + * 32 extra characters for growth. + * + * @param str the string to copy, null treated as blank string + */ + public StrBuilder(final String str) { + super(); + if (str == null) { + buffer = new char[CAPACITY]; + } else { + buffer = new char[str.length() + CAPACITY]; + append(str); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the text to be appended when a new line is added. + * + * @return the new line text, null means use system default + */ + public String getNewLineText() { + return newLine; + } + + /** + * Sets the text to be appended when a new line is added. + * + * @param newLine the new line text, null means use system default + * @return this, to enable chaining + */ + public StrBuilder setNewLineText(final String newLine) { + this.newLine = newLine; + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the text to be appended when null is added. + * + * @return the null text, null means no append + */ + public String getNullText() { + return nullText; + } + + /** + * Sets the text to be appended when null is added. + * + * @param nullText the null text, null means no append + * @return this, to enable chaining + */ + public StrBuilder setNullText(String nullText) { + if (nullText != null && nullText.isEmpty()) { + nullText = null; + } + this.nullText = nullText; + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the length of the string builder. + * + * @return the length + */ + @Override + public int length() { + return size; + } + + /** + * Updates the length of the builder by either dropping the last characters + * or adding filler of Unicode zero. + * + * @param length the length to set to, must be zero or positive + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the length is negative + */ + public StrBuilder setLength(final int length) { + if (length < 0) { + throw new StringIndexOutOfBoundsException(length); + } + if (length < size) { + size = length; + } else if (length > size) { + ensureCapacity(length); + final int oldEnd = size; + final int newEnd = length; + size = length; + for (int i = oldEnd; i < newEnd; i++) { + buffer[i] = '\0'; + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the current size of the internal character array buffer. + * + * @return the capacity + */ + public int capacity() { + return buffer.length; + } + + /** + * Checks the capacity and ensures that it is at least the size specified. + * + * @param capacity the capacity to ensure + * @return this, to enable chaining + */ + public StrBuilder ensureCapacity(final int capacity) { + if (capacity > buffer.length) { + final char[] old = buffer; + buffer = new char[capacity * 2]; + System.arraycopy(old, 0, buffer, 0, size); + } + return this; + } + + /** + * Minimizes the capacity to the actual length of the string. + * + * @return this, to enable chaining + */ + public StrBuilder minimizeCapacity() { + if (buffer.length > length()) { + final char[] old = buffer; + buffer = new char[length()]; + System.arraycopy(old, 0, buffer, 0, size); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the length of the string builder. + *

              + * This method is the same as {@link #length()} and is provided to match the + * API of Collections. + * + * @return the length + */ + public int size() { + return size; + } + + /** + * Checks is the string builder is empty (convenience Collections API style method). + *

              + * This method is the same as checking {@link #length()} and is provided to match the + * API of Collections. + * + * @return true if the size is 0. + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Clears the string builder (convenience Collections API style method). + *

              + * This method does not reduce the size of the internal character buffer. + * To do that, call clear() followed by {@link #minimizeCapacity()}. + *

              + * This method is the same as {@link #setLength(int)} called with zero + * and is provided to match the API of Collections. + * + * @return this, to enable chaining + */ + public StrBuilder clear() { + size = 0; + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the character at the specified index. + * + * @see #setCharAt(int, char) + * @see #deleteCharAt(int) + * @param index the index to retrieve, must be valid + * @return the character at the index + * @throws IndexOutOfBoundsException if the index is invalid + */ + @Override + public char charAt(final int index) { + if (index < 0 || index >= length()) { + throw new StringIndexOutOfBoundsException(index); + } + return buffer[index]; + } + + /** + * Sets the character at the specified index. + * + * @see #charAt(int) + * @see #deleteCharAt(int) + * @param index the index to set + * @param ch the new character + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder setCharAt(final int index, final char ch) { + if (index < 0 || index >= length()) { + throw new StringIndexOutOfBoundsException(index); + } + buffer[index] = ch; + return this; + } + + /** + * Deletes the character at the specified index. + * + * @see #charAt(int) + * @see #setCharAt(int, char) + * @param index the index to delete + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder deleteCharAt(final int index) { + if (index < 0 || index >= size) { + throw new StringIndexOutOfBoundsException(index); + } + deleteImpl(index, index + 1, 1); + return this; + } + + //----------------------------------------------------------------------- + /** + * Copies the builder's character array into a new character array. + * + * @return a new array that represents the contents of the builder + */ + public char[] toCharArray() { + if (size == 0) { + return ArrayUtils.EMPTY_CHAR_ARRAY; + } + final char chars[] = new char[size]; + System.arraycopy(buffer, 0, chars, 0, size); + return chars; + } + + /** + * Copies part of the builder's character array into a new character array. + * + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except that + * if too large it is treated as end of string + * @return a new array that holds part of the contents of the builder + * @throws IndexOutOfBoundsException if startIndex is invalid, + * or if endIndex is invalid (but endIndex greater than size is valid) + */ + public char[] toCharArray(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + final int len = endIndex - startIndex; + if (len == 0) { + return ArrayUtils.EMPTY_CHAR_ARRAY; + } + final char chars[] = new char[len]; + System.arraycopy(buffer, startIndex, chars, 0, len); + return chars; + } + + /** + * Copies the character array into the specified array. + * + * @param destination the destination array, null will cause an array to be created + * @return the input array, unless that was null or too small + */ + public char[] getChars(char[] destination) { + final int len = length(); + if (destination == null || destination.length < len) { + destination = new char[len]; + } + System.arraycopy(buffer, 0, destination, 0, len); + return destination; + } + + /** + * Copies the character array into the specified array. + * + * @param startIndex first index to copy, inclusive, must be valid + * @param endIndex last index, exclusive, must be valid + * @param destination the destination array, must not be null or too small + * @param destinationIndex the index to start copying in destination + * @throws NullPointerException if the array is null + * @throws IndexOutOfBoundsException if any index is invalid + */ + public void getChars(final int startIndex, final int endIndex, final char destination[], final int destinationIndex) { + if (startIndex < 0) { + throw new StringIndexOutOfBoundsException(startIndex); + } + if (endIndex < 0 || endIndex > length()) { + throw new StringIndexOutOfBoundsException(endIndex); + } + if (startIndex > endIndex) { + throw new StringIndexOutOfBoundsException("end < start"); + } + System.arraycopy(buffer, startIndex, destination, destinationIndex, endIndex - startIndex); + } + + //----------------------------------------------------------------------- + /** + * If possible, reads chars from the provided {@link Readable} directly into underlying + * character buffer without making extra copies. + * + * @param readable object to read from + * @return the number of characters read + * @throws IOException if an I/O error occurs + * + * @since 3.4 + * @see #appendTo(Appendable) + */ + public int readFrom(final Readable readable) throws IOException { + final int oldSize = size; + if (readable instanceof Reader) { + final Reader r = (Reader) readable; + ensureCapacity(size + 1); + int read; + while ((read = r.read(buffer, size, buffer.length - size)) != -1) { + size += read; + ensureCapacity(size + 1); + } + } else if (readable instanceof CharBuffer) { + final CharBuffer cb = (CharBuffer) readable; + final int remaining = cb.remaining(); + ensureCapacity(size + remaining); + cb.get(buffer, size, remaining); + size += remaining; + } else { + while (true) { + ensureCapacity(size + 1); + final CharBuffer buf = CharBuffer.wrap(buffer, size, buffer.length - size); + final int read = readable.read(buf); + if (read == -1) { + break; + } + size += read; + } + } + return size - oldSize; + } + + //----------------------------------------------------------------------- + /** + * Appends the new line string to this string builder. + *

              + * The new line string can be altered using {@link #setNewLineText(String)}. + * This might be used to force the output to always use Unix line endings + * even when on Windows. + * + * @return this, to enable chaining + */ + public StrBuilder appendNewLine() { + if (newLine == null) { + append(SystemUtils.LINE_SEPARATOR); + return this; + } + return append(newLine); + } + + /** + * Appends the text representing null to this string builder. + * + * @return this, to enable chaining + */ + public StrBuilder appendNull() { + if (nullText == null) { + return this; + } + return append(nullText); + } + + /** + * Appends an object to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param obj the object to append + * @return this, to enable chaining + */ + public StrBuilder append(final Object obj) { + if (obj == null) { + return appendNull(); + } + if (obj instanceof CharSequence) { + return append((CharSequence) obj); + } + return append(obj.toString()); + } + + /** + * Appends a CharSequence to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param seq the CharSequence to append + * @return this, to enable chaining + * @since 3.0 + */ + @Override + public StrBuilder append(final CharSequence seq) { + if (seq == null) { + return appendNull(); + } + if (seq instanceof StrBuilder) { + return append((StrBuilder) seq); + } + if (seq instanceof StringBuilder) { + return append((StringBuilder) seq); + } + if (seq instanceof StringBuffer) { + return append((StringBuffer) seq); + } + if (seq instanceof CharBuffer) { + return append((CharBuffer) seq); + } + return append(seq.toString()); + } + + /** + * Appends part of a CharSequence to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param seq the CharSequence to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 3.0 + */ + @Override + public StrBuilder append(final CharSequence seq, final int startIndex, final int length) { + if (seq == null) { + return appendNull(); + } + return append(seq.toString(), startIndex, length); + } + + /** + * Appends a string to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @return this, to enable chaining + */ + public StrBuilder append(final String str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + str.getChars(0, strLen, buffer, len); + size += strLen; + } + return this; + } + + + /** + * Appends part of a string to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + */ + public StrBuilder append(final String str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > str.length()) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || (startIndex + length) > str.length()) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += length; + } + return this; + } + + /** + * Calls {@link String#format(String, Object...)} and appends the result. + * + * @param format the format string + * @param objs the objects to use in the format string + * @return {@code this} to enable chaining + * @see String#format(String, Object...) + * @since 3.2 + */ + public StrBuilder append(final String format, final Object... objs) { + return append(String.format(format, objs)); + } + + /** + * Appends the contents of a char buffer to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param buf the char buffer to append + * @return this, to enable chaining + * @since 3.4 + */ + public StrBuilder append(final CharBuffer buf) { + if (buf == null) { + return appendNull(); + } + if (buf.hasArray()) { + final int length = buf.remaining(); + final int len = length(); + ensureCapacity(len + length); + System.arraycopy(buf.array(), buf.arrayOffset() + buf.position(), buffer, len, length); + size += length; + } else { + append(buf.toString()); + } + return this; + } + + /** + * Appends the contents of a char buffer to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param buf the char buffer to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 3.4 + */ + public StrBuilder append(final CharBuffer buf, final int startIndex, final int length) { + if (buf == null) { + return appendNull(); + } + if (buf.hasArray()) { + final int totalLength = buf.remaining(); + if (startIndex < 0 || startIndex > totalLength) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || (startIndex + length) > totalLength) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + final int len = length(); + ensureCapacity(len + length); + System.arraycopy(buf.array(), buf.arrayOffset() + buf.position() + startIndex, buffer, len, length); + size += length; + } else { + append(buf.toString(), startIndex, length); + } + return this; + } + + /** + * Appends a string buffer to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string buffer to append + * @return this, to enable chaining + */ + public StrBuilder append(final StringBuffer str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + str.getChars(0, strLen, buffer, len); + size += strLen; + } + return this; + } + + /** + * Appends part of a string buffer to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + */ + public StrBuilder append(final StringBuffer str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > str.length()) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || (startIndex + length) > str.length()) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += length; + } + return this; + } + + /** + * Appends a StringBuilder to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the StringBuilder to append + * @return this, to enable chaining + * @since 3.2 + */ + public StrBuilder append(final StringBuilder str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + str.getChars(0, strLen, buffer, len); + size += strLen; + } + return this; + } + + /** + * Appends part of a StringBuilder to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the StringBuilder to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 3.2 + */ + public StrBuilder append(final StringBuilder str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > str.length()) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || (startIndex + length) > str.length()) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += length; + } + return this; + } + + /** + * Appends another string builder to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string builder to append + * @return this, to enable chaining + */ + public StrBuilder append(final StrBuilder str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + System.arraycopy(str.buffer, 0, buffer, len, strLen); + size += strLen; + } + return this; + } + + /** + * Appends part of a string builder to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + */ + public StrBuilder append(final StrBuilder str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > str.length()) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || (startIndex + length) > str.length()) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += length; + } + return this; + } + + /** + * Appends a char array to the string builder. + * Appending null will call {@link #appendNull()}. + * + * @param chars the char array to append + * @return this, to enable chaining + */ + public StrBuilder append(final char[] chars) { + if (chars == null) { + return appendNull(); + } + final int strLen = chars.length; + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + System.arraycopy(chars, 0, buffer, len, strLen); + size += strLen; + } + return this; + } + + /** + * Appends a char array to the string builder. + * Appending null will call {@link #appendNull()}. + * + * @param chars the char array to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + */ + public StrBuilder append(final char[] chars, final int startIndex, final int length) { + if (chars == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid startIndex: " + length); + } + if (length < 0 || (startIndex + length) > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid length: " + length); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + System.arraycopy(chars, startIndex, buffer, len, length); + size += length; + } + return this; + } + + /** + * Appends a boolean value to the string builder. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(final boolean value) { + if (value) { + ensureCapacity(size + 4); + buffer[size++] = 't'; + buffer[size++] = 'r'; + buffer[size++] = 'u'; + buffer[size++] = 'e'; + } else { + ensureCapacity(size + 5); + buffer[size++] = 'f'; + buffer[size++] = 'a'; + buffer[size++] = 'l'; + buffer[size++] = 's'; + buffer[size++] = 'e'; + } + return this; + } + + /** + * Appends a char value to the string builder. + * + * @param ch the value to append + * @return this, to enable chaining + * @since 3.0 + */ + @Override + public StrBuilder append(final char ch) { + final int len = length(); + ensureCapacity(len + 1); + buffer[size++] = ch; + return this; + } + + /** + * Appends an int value to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(final int value) { + return append(String.valueOf(value)); + } + + /** + * Appends a long value to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(final long value) { + return append(String.valueOf(value)); + } + + /** + * Appends a float value to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(final float value) { + return append(String.valueOf(value)); + } + + /** + * Appends a double value to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(final double value) { + return append(String.valueOf(value)); + } + + //----------------------------------------------------------------------- + /** + * Appends an object followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param obj the object to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final Object obj) { + return append(obj).appendNewLine(); + } + + /** + * Appends a string followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final String str) { + return append(str).appendNewLine(); + } + + /** + * Appends part of a string followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final String str, final int startIndex, final int length) { + return append(str, startIndex, length).appendNewLine(); + } + + /** + * Calls {@link String#format(String, Object...)} and appends the result. + * + * @param format the format string + * @param objs the objects to use in the format string + * @return {@code this} to enable chaining + * @see String#format(String, Object...) + * @since 3.2 + */ + public StrBuilder appendln(final String format, final Object... objs) { + return append(format, objs).appendNewLine(); + } + + /** + * Appends a string buffer followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string buffer to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final StringBuffer str) { + return append(str).appendNewLine(); + } + + /** + * Appends a string builder followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string builder to append + * @return this, to enable chaining + * @since 3.2 + */ + public StrBuilder appendln(final StringBuilder str) { + return append(str).appendNewLine(); + } + + /** + * Appends part of a string builder followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string builder to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 3.2 + */ + public StrBuilder appendln(final StringBuilder str, final int startIndex, final int length) { + return append(str, startIndex, length).appendNewLine(); + } + + /** + * Appends part of a string buffer followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final StringBuffer str, final int startIndex, final int length) { + return append(str, startIndex, length).appendNewLine(); + } + + /** + * Appends another string builder followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string builder to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final StrBuilder str) { + return append(str).appendNewLine(); + } + + /** + * Appends part of a string builder followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final StrBuilder str, final int startIndex, final int length) { + return append(str, startIndex, length).appendNewLine(); + } + + /** + * Appends a char array followed by a new line to the string builder. + * Appending null will call {@link #appendNull()}. + * + * @param chars the char array to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final char[] chars) { + return append(chars).appendNewLine(); + } + + /** + * Appends a char array followed by a new line to the string builder. + * Appending null will call {@link #appendNull()}. + * + * @param chars the char array to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final char[] chars, final int startIndex, final int length) { + return append(chars, startIndex, length).appendNewLine(); + } + + /** + * Appends a boolean value followed by a new line to the string builder. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final boolean value) { + return append(value).appendNewLine(); + } + + /** + * Appends a char value followed by a new line to the string builder. + * + * @param ch the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final char ch) { + return append(ch).appendNewLine(); + } + + /** + * Appends an int value followed by a new line to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final int value) { + return append(value).appendNewLine(); + } + + /** + * Appends a long value followed by a new line to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final long value) { + return append(value).appendNewLine(); + } + + /** + * Appends a float value followed by a new line to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final float value) { + return append(value).appendNewLine(); + } + + /** + * Appends a double value followed by a new line to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final double value) { + return append(value).appendNewLine(); + } + + //----------------------------------------------------------------------- + /** + * Appends each item in an array to the builder without any separators. + * Appending a null array will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param the element type + * @param array the array to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendAll(final T... array) { + if (array != null && array.length > 0) { + for (final Object element : array) { + append(element); + } + } + return this; + } + + /** + * Appends each item in a iterable to the builder without any separators. + * Appending a null iterable will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param iterable the iterable to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendAll(final Iterable iterable) { + if (iterable != null) { + for (final Object o : iterable) { + append(o); + } + } + return this; + } + + /** + * Appends each item in an iterator to the builder without any separators. + * Appending a null iterator will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param it the iterator to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendAll(final Iterator it) { + if (it != null) { + while (it.hasNext()) { + append(it.next()); + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends an array placing separators between each value, but + * not before the first or after the last. + * Appending a null array will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param array the array to append + * @param separator the separator to use, null means no separator + * @return this, to enable chaining + */ + public StrBuilder appendWithSeparators(final Object[] array, final String separator) { + if (array != null && array.length > 0) { + @SuppressWarnings( "deprecation" ) // ObjectUtils.toString(Object) has been deprecated in 3.2 + final String sep = ObjectUtils.toString(separator); + append(array[0]); + for (int i = 1; i < array.length; i++) { + append(sep); + append(array[i]); + } + } + return this; + } + + /** + * Appends a iterable placing separators between each value, but + * not before the first or after the last. + * Appending a null iterable will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param iterable the iterable to append + * @param separator the separator to use, null means no separator + * @return this, to enable chaining + */ + public StrBuilder appendWithSeparators(final Iterable iterable, final String separator) { + if (iterable != null) { + @SuppressWarnings( "deprecation" ) // ObjectUtils.toString(Object) has been deprecated in 3.2 + final String sep = ObjectUtils.toString(separator); + final Iterator it = iterable.iterator(); + while (it.hasNext()) { + append(it.next()); + if (it.hasNext()) { + append(sep); + } + } + } + return this; + } + + /** + * Appends an iterator placing separators between each value, but + * not before the first or after the last. + * Appending a null iterator will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param it the iterator to append + * @param separator the separator to use, null means no separator + * @return this, to enable chaining + */ + public StrBuilder appendWithSeparators(final Iterator it, final String separator) { + if (it != null) { + @SuppressWarnings( "deprecation" ) // ObjectUtils.toString(Object) has been deprecated in 3.2 + final String sep = ObjectUtils.toString(separator); + while (it.hasNext()) { + append(it.next()); + if (it.hasNext()) { + append(sep); + } + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends a separator if the builder is currently non-empty. + * Appending a null separator will have no effect. + * The separator is appended using {@link #append(String)}. + *

              + * This method is useful for adding a separator each time around the + * loop except the first. + *

              +     * for (Iterator it = list.iterator(); it.hasNext(); ) {
              +     *   appendSeparator(",");
              +     *   append(it.next());
              +     * }
              +     * 
              + * Note that for this simple example, you should use + * {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator the separator to use, null means no separator + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendSeparator(final String separator) { + return appendSeparator(separator, null); + } + + /** + * Appends one of both separators to the StrBuilder. + * If the builder is currently empty it will append the defaultIfEmpty-separator + * Otherwise it will append the standard-separator + * + * Appending a null separator will have no effect. + * The separator is appended using {@link #append(String)}. + *

              + * This method is for example useful for constructing queries + *

              +     * StrBuilder whereClause = new StrBuilder();
              +     * if(searchCommand.getPriority() != null) {
              +     *  whereClause.appendSeparator(" and", " where");
              +     *  whereClause.append(" priority = ?")
              +     * }
              +     * if(searchCommand.getComponent() != null) {
              +     *  whereClause.appendSeparator(" and", " where");
              +     *  whereClause.append(" component = ?")
              +     * }
              +     * selectClause.append(whereClause)
              +     * 
              + * + * @param standard the separator if builder is not empty, null means no separator + * @param defaultIfEmpty the separator if builder is empty, null means no separator + * @return this, to enable chaining + * @since 2.5 + */ + public StrBuilder appendSeparator(final String standard, final String defaultIfEmpty) { + final String str = isEmpty() ? defaultIfEmpty : standard; + if (str != null) { + append(str); + } + return this; + } + + /** + * Appends a separator if the builder is currently non-empty. + * The separator is appended using {@link #append(char)}. + *

              + * This method is useful for adding a separator each time around the + * loop except the first. + *

              +     * for (Iterator it = list.iterator(); it.hasNext(); ) {
              +     *   appendSeparator(',');
              +     *   append(it.next());
              +     * }
              +     * 
              + * Note that for this simple example, you should use + * {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator the separator to use + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendSeparator(final char separator) { + if (size() > 0) { + append(separator); + } + return this; + } + + /** + * Append one of both separators to the builder + * If the builder is currently empty it will append the defaultIfEmpty-separator + * Otherwise it will append the standard-separator + * + * The separator is appended using {@link #append(char)}. + * @param standard the separator if builder is not empty + * @param defaultIfEmpty the separator if builder is empty + * @return this, to enable chaining + * @since 2.5 + */ + public StrBuilder appendSeparator(final char standard, final char defaultIfEmpty) { + if (size() > 0) { + append(standard); + } else { + append(defaultIfEmpty); + } + return this; + } + /** + * Appends a separator to the builder if the loop index is greater than zero. + * Appending a null separator will have no effect. + * The separator is appended using {@link #append(String)}. + *

              + * This method is useful for adding a separator each time around the + * loop except the first. + *

              + *
              +     * for (int i = 0; i < list.size(); i++) {
              +     *   appendSeparator(",", i);
              +     *   append(list.get(i));
              +     * }
              +     * 
              + * Note that for this simple example, you should use + * {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator the separator to use, null means no separator + * @param loopIndex the loop index + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendSeparator(final String separator, final int loopIndex) { + if (separator != null && loopIndex > 0) { + append(separator); + } + return this; + } + + /** + * Appends a separator to the builder if the loop index is greater than zero. + * The separator is appended using {@link #append(char)}. + *

              + * This method is useful for adding a separator each time around the + * loop except the first. + *

              + *
              +     * for (int i = 0; i < list.size(); i++) {
              +     *   appendSeparator(",", i);
              +     *   append(list.get(i));
              +     * }
              +     * 
              + * Note that for this simple example, you should use + * {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator the separator to use + * @param loopIndex the loop index + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendSeparator(final char separator, final int loopIndex) { + if (loopIndex > 0) { + append(separator); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends the pad character to the builder the specified number of times. + * + * @param length the length to append, negative means no append + * @param padChar the character to append + * @return this, to enable chaining + */ + public StrBuilder appendPadding(final int length, final char padChar) { + if (length >= 0) { + ensureCapacity(size + length); + for (int i = 0; i < length; i++) { + buffer[size++] = padChar; + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends an object to the builder padding on the left to a fixed width. + * The toString of the object is used. + * If the object is larger than the length, the left hand side is lost. + * If the object is null, the null text value is used. + * + * @param obj the object to append, null uses null text + * @param width the fixed field width, zero or negative has no effect + * @param padChar the pad character to use + * @return this, to enable chaining + */ + public StrBuilder appendFixedWidthPadLeft(final Object obj, final int width, final char padChar) { + if (width > 0) { + ensureCapacity(size + width); + String str = (obj == null ? getNullText() : obj.toString()); + if (str == null) { + str = ""; + } + final int strLen = str.length(); + if (strLen >= width) { + str.getChars(strLen - width, strLen, buffer, size); + } else { + final int padLen = width - strLen; + for (int i = 0; i < padLen; i++) { + buffer[size + i] = padChar; + } + str.getChars(0, strLen, buffer, size + padLen); + } + size += width; + } + return this; + } + + /** + * Appends an object to the builder padding on the left to a fixed width. + * The String.valueOf of the int value is used. + * If the formatted value is larger than the length, the left hand side is lost. + * + * @param value the value to append + * @param width the fixed field width, zero or negative has no effect + * @param padChar the pad character to use + * @return this, to enable chaining + */ + public StrBuilder appendFixedWidthPadLeft(final int value, final int width, final char padChar) { + return appendFixedWidthPadLeft(String.valueOf(value), width, padChar); + } + + /** + * Appends an object to the builder padding on the right to a fixed length. + * The toString of the object is used. + * If the object is larger than the length, the right hand side is lost. + * If the object is null, null text value is used. + * + * @param obj the object to append, null uses null text + * @param width the fixed field width, zero or negative has no effect + * @param padChar the pad character to use + * @return this, to enable chaining + */ + public StrBuilder appendFixedWidthPadRight(final Object obj, final int width, final char padChar) { + if (width > 0) { + ensureCapacity(size + width); + String str = (obj == null ? getNullText() : obj.toString()); + if (str == null) { + str = ""; + } + final int strLen = str.length(); + if (strLen >= width) { + str.getChars(0, width, buffer, size); + } else { + final int padLen = width - strLen; + str.getChars(0, strLen, buffer, size); + for (int i = 0; i < padLen; i++) { + buffer[size + strLen + i] = padChar; + } + } + size += width; + } + return this; + } + + /** + * Appends an object to the builder padding on the right to a fixed length. + * The String.valueOf of the int value is used. + * If the object is larger than the length, the right hand side is lost. + * + * @param value the value to append + * @param width the fixed field width, zero or negative has no effect + * @param padChar the pad character to use + * @return this, to enable chaining + */ + public StrBuilder appendFixedWidthPadRight(final int value, final int width, final char padChar) { + return appendFixedWidthPadRight(String.valueOf(value), width, padChar); + } + + //----------------------------------------------------------------------- + /** + * Inserts the string representation of an object into this builder. + * Inserting null will use the stored null text value. + * + * @param index the index to add at, must be valid + * @param obj the object to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final Object obj) { + if (obj == null) { + return insert(index, nullText); + } + return insert(index, obj.toString()); + } + + /** + * Inserts the string into this builder. + * Inserting null will use the stored null text value. + * + * @param index the index to add at, must be valid + * @param str the string to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, String str) { + validateIndex(index); + if (str == null) { + str = nullText; + } + if (str != null) { + final int strLen = str.length(); + if (strLen > 0) { + final int newSize = size + strLen; + ensureCapacity(newSize); + System.arraycopy(buffer, index, buffer, index + strLen, size - index); + size = newSize; + str.getChars(0, strLen, buffer, index); + } + } + return this; + } + + /** + * Inserts the character array into this builder. + * Inserting null will use the stored null text value. + * + * @param index the index to add at, must be valid + * @param chars the char array to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final char chars[]) { + validateIndex(index); + if (chars == null) { + return insert(index, nullText); + } + final int len = chars.length; + if (len > 0) { + ensureCapacity(size + len); + System.arraycopy(buffer, index, buffer, index + len, size - index); + System.arraycopy(chars, 0, buffer, index, len); + size += len; + } + return this; + } + + /** + * Inserts part of the character array into this builder. + * Inserting null will use the stored null text value. + * + * @param index the index to add at, must be valid + * @param chars the char array to insert + * @param offset the offset into the character array to start at, must be valid + * @param length the length of the character array part to copy, must be positive + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if any index is invalid + */ + public StrBuilder insert(final int index, final char chars[], final int offset, final int length) { + validateIndex(index); + if (chars == null) { + return insert(index, nullText); + } + if (offset < 0 || offset > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid offset: " + offset); + } + if (length < 0 || offset + length > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid length: " + length); + } + if (length > 0) { + ensureCapacity(size + length); + System.arraycopy(buffer, index, buffer, index + length, size - index); + System.arraycopy(chars, offset, buffer, index, length); + size += length; + } + return this; + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(int index, final boolean value) { + validateIndex(index); + if (value) { + ensureCapacity(size + 4); + System.arraycopy(buffer, index, buffer, index + 4, size - index); + buffer[index++] = 't'; + buffer[index++] = 'r'; + buffer[index++] = 'u'; + buffer[index] = 'e'; + size += 4; + } else { + ensureCapacity(size + 5); + System.arraycopy(buffer, index, buffer, index + 5, size - index); + buffer[index++] = 'f'; + buffer[index++] = 'a'; + buffer[index++] = 'l'; + buffer[index++] = 's'; + buffer[index] = 'e'; + size += 5; + } + return this; + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final char value) { + validateIndex(index); + ensureCapacity(size + 1); + System.arraycopy(buffer, index, buffer, index + 1, size - index); + buffer[index] = value; + size++; + return this; + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final int value) { + return insert(index, String.valueOf(value)); + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final long value) { + return insert(index, String.valueOf(value)); + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final float value) { + return insert(index, String.valueOf(value)); + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final double value) { + return insert(index, String.valueOf(value)); + } + + //----------------------------------------------------------------------- + /** + * Internal method to delete a range without validation. + * + * @param startIndex the start index, must be valid + * @param endIndex the end index (exclusive), must be valid + * @param len the length, must be valid + * @throws IndexOutOfBoundsException if any index is invalid + */ + private void deleteImpl(final int startIndex, final int endIndex, final int len) { + System.arraycopy(buffer, endIndex, buffer, startIndex, size - endIndex); + size -= len; + } + + /** + * Deletes the characters between the two specified indices. + * + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder delete(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + final int len = endIndex - startIndex; + if (len > 0) { + deleteImpl(startIndex, endIndex, len); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Deletes the character wherever it occurs in the builder. + * + * @param ch the character to delete + * @return this, to enable chaining + */ + public StrBuilder deleteAll(final char ch) { + for (int i = 0; i < size; i++) { + if (buffer[i] == ch) { + final int start = i; + while (++i < size) { + if (buffer[i] != ch) { + break; + } + } + final int len = i - start; + deleteImpl(start, i, len); + i -= len; + } + } + return this; + } + + /** + * Deletes the character wherever it occurs in the builder. + * + * @param ch the character to delete + * @return this, to enable chaining + */ + public StrBuilder deleteFirst(final char ch) { + for (int i = 0; i < size; i++) { + if (buffer[i] == ch) { + deleteImpl(i, i + 1, 1); + break; + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Deletes the string wherever it occurs in the builder. + * + * @param str the string to delete, null causes no action + * @return this, to enable chaining + */ + public StrBuilder deleteAll(final String str) { + final int len = (str == null ? 0 : str.length()); + if (len > 0) { + int index = indexOf(str, 0); + while (index >= 0) { + deleteImpl(index, index + len, len); + index = indexOf(str, index); + } + } + return this; + } + + /** + * Deletes the string wherever it occurs in the builder. + * + * @param str the string to delete, null causes no action + * @return this, to enable chaining + */ + public StrBuilder deleteFirst(final String str) { + final int len = (str == null ? 0 : str.length()); + if (len > 0) { + final int index = indexOf(str, 0); + if (index >= 0) { + deleteImpl(index, index + len, len); + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Deletes all parts of the builder that the matcher matches. + *

              + * Matchers can be used to perform advanced deletion behaviour. + * For example you could write a matcher to delete all occurrences + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @return this, to enable chaining + */ + public StrBuilder deleteAll(final StrMatcher matcher) { + return replace(matcher, null, 0, size, -1); + } + + /** + * Deletes the first match within the builder using the specified matcher. + *

              + * Matchers can be used to perform advanced deletion behaviour. + * For example you could write a matcher to delete + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @return this, to enable chaining + */ + public StrBuilder deleteFirst(final StrMatcher matcher) { + return replace(matcher, null, 0, size, 1); + } + + //----------------------------------------------------------------------- + /** + * Internal method to delete a range without validation. + * + * @param startIndex the start index, must be valid + * @param endIndex the end index (exclusive), must be valid + * @param removeLen the length to remove (endIndex - startIndex), must be valid + * @param insertStr the string to replace with, null means delete range + * @param insertLen the length of the insert string, must be valid + * @throws IndexOutOfBoundsException if any index is invalid + */ + private void replaceImpl(final int startIndex, final int endIndex, final int removeLen, final String insertStr, final int insertLen) { + final int newSize = size - removeLen + insertLen; + if (insertLen != removeLen) { + ensureCapacity(newSize); + System.arraycopy(buffer, endIndex, buffer, startIndex + insertLen, size - endIndex); + size = newSize; + } + if (insertLen > 0) { + insertStr.getChars(0, insertLen, buffer, startIndex); + } + } + + /** + * Replaces a portion of the string builder with another string. + * The length of the inserted string does not have to match the removed length. + * + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @param replaceStr the string to replace with, null means delete range + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder replace(final int startIndex, int endIndex, final String replaceStr) { + endIndex = validateRange(startIndex, endIndex); + final int insertLen = (replaceStr == null ? 0 : replaceStr.length()); + replaceImpl(startIndex, endIndex, endIndex - startIndex, replaceStr, insertLen); + return this; + } + + //----------------------------------------------------------------------- + /** + * Replaces the search character with the replace character + * throughout the builder. + * + * @param search the search character + * @param replace the replace character + * @return this, to enable chaining + */ + public StrBuilder replaceAll(final char search, final char replace) { + if (search != replace) { + for (int i = 0; i < size; i++) { + if (buffer[i] == search) { + buffer[i] = replace; + } + } + } + return this; + } + + /** + * Replaces the first instance of the search character with the + * replace character in the builder. + * + * @param search the search character + * @param replace the replace character + * @return this, to enable chaining + */ + public StrBuilder replaceFirst(final char search, final char replace) { + if (search != replace) { + for (int i = 0; i < size; i++) { + if (buffer[i] == search) { + buffer[i] = replace; + break; + } + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Replaces the search string with the replace string throughout the builder. + * + * @param searchStr the search string, null causes no action to occur + * @param replaceStr the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public StrBuilder replaceAll(final String searchStr, final String replaceStr) { + final int searchLen = (searchStr == null ? 0 : searchStr.length()); + if (searchLen > 0) { + final int replaceLen = (replaceStr == null ? 0 : replaceStr.length()); + int index = indexOf(searchStr, 0); + while (index >= 0) { + replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen); + index = indexOf(searchStr, index + replaceLen); + } + } + return this; + } + + /** + * Replaces the first instance of the search string with the replace string. + * + * @param searchStr the search string, null causes no action to occur + * @param replaceStr the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public StrBuilder replaceFirst(final String searchStr, final String replaceStr) { + final int searchLen = (searchStr == null ? 0 : searchStr.length()); + if (searchLen > 0) { + final int index = indexOf(searchStr, 0); + if (index >= 0) { + final int replaceLen = (replaceStr == null ? 0 : replaceStr.length()); + replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen); + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Replaces all matches within the builder with the replace string. + *

              + * Matchers can be used to perform advanced replace behaviour. + * For example you could write a matcher to replace all occurrences + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public StrBuilder replaceAll(final StrMatcher matcher, final String replaceStr) { + return replace(matcher, replaceStr, 0, size, -1); + } + + /** + * Replaces the first match within the builder with the replace string. + *

              + * Matchers can be used to perform advanced replace behaviour. + * For example you could write a matcher to replace + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public StrBuilder replaceFirst(final StrMatcher matcher, final String replaceStr) { + return replace(matcher, replaceStr, 0, size, 1); + } + + // ----------------------------------------------------------------------- + /** + * Advanced search and replaces within the builder using a matcher. + *

              + * Matchers can be used to perform advanced behaviour. + * For example you could write a matcher to delete all occurrences + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the string to replace the match with, null is a delete + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @param replaceCount the number of times to replace, -1 for replace all + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if start index is invalid + */ + public StrBuilder replace( + final StrMatcher matcher, final String replaceStr, + final int startIndex, int endIndex, final int replaceCount) { + endIndex = validateRange(startIndex, endIndex); + return replaceImpl(matcher, replaceStr, startIndex, endIndex, replaceCount); + } + + /** + * Replaces within the builder using a matcher. + *

              + * Matchers can be used to perform advanced behaviour. + * For example you could write a matcher to delete all occurrences + * where the character 'a' is followed by a number. + * + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the string to replace the match with, null is a delete + * @param from the start index, must be valid + * @param to the end index (exclusive), must be valid + * @param replaceCount the number of times to replace, -1 for replace all + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if any index is invalid + */ + private StrBuilder replaceImpl( + final StrMatcher matcher, final String replaceStr, + final int from, int to, int replaceCount) { + if (matcher == null || size == 0) { + return this; + } + final int replaceLen = (replaceStr == null ? 0 : replaceStr.length()); + final char[] buf = buffer; + for (int i = from; i < to && replaceCount != 0; i++) { + final int removeLen = matcher.isMatch(buf, i, from, to); + if (removeLen > 0) { + replaceImpl(i, i + removeLen, removeLen, replaceStr, replaceLen); + to = to - removeLen + replaceLen; + i = i + replaceLen - 1; + if (replaceCount > 0) { + replaceCount--; + } + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Reverses the string builder placing each character in the opposite index. + * + * @return this, to enable chaining + */ + public StrBuilder reverse() { + if (size == 0) { + return this; + } + + final int half = size / 2; + final char[] buf = buffer; + for (int leftIdx = 0, rightIdx = size - 1; leftIdx < half; leftIdx++,rightIdx--) { + final char swap = buf[leftIdx]; + buf[leftIdx] = buf[rightIdx]; + buf[rightIdx] = swap; + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Trims the builder by removing characters less than or equal to a space + * from the beginning and end. + * + * @return this, to enable chaining + */ + public StrBuilder trim() { + if (size == 0) { + return this; + } + int len = size; + final char[] buf = buffer; + int pos = 0; + while (pos < len && buf[pos] <= ' ') { + pos++; + } + while (pos < len && buf[len - 1] <= ' ') { + len--; + } + if (len < size) { + delete(len, size); + } + if (pos > 0) { + delete(0, pos); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Checks whether this builder starts with the specified string. + *

              + * Note that this method handles null input quietly, unlike String. + * + * @param str the string to search for, null returns false + * @return true if the builder starts with the string + */ + public boolean startsWith(final String str) { + if (str == null) { + return false; + } + final int len = str.length(); + if (len == 0) { + return true; + } + if (len > size) { + return false; + } + for (int i = 0; i < len; i++) { + if (buffer[i] != str.charAt(i)) { + return false; + } + } + return true; + } + + /** + * Checks whether this builder ends with the specified string. + *

              + * Note that this method handles null input quietly, unlike String. + * + * @param str the string to search for, null returns false + * @return true if the builder ends with the string + */ + public boolean endsWith(final String str) { + if (str == null) { + return false; + } + final int len = str.length(); + if (len == 0) { + return true; + } + if (len > size) { + return false; + } + int pos = size - len; + for (int i = 0; i < len; i++,pos++) { + if (buffer[pos] != str.charAt(i)) { + return false; + } + } + return true; + } + + //----------------------------------------------------------------------- + /** + * {@inheritDoc} + * @since 3.0 + */ + @Override + public CharSequence subSequence(final int startIndex, final int endIndex) { + if (startIndex < 0) { + throw new StringIndexOutOfBoundsException(startIndex); + } + if (endIndex > size) { + throw new StringIndexOutOfBoundsException(endIndex); + } + if (startIndex > endIndex) { + throw new StringIndexOutOfBoundsException(endIndex - startIndex); + } + return substring(startIndex, endIndex); + } + + /** + * Extracts a portion of this string builder as a string. + * + * @param start the start index, inclusive, must be valid + * @return the new string + * @throws IndexOutOfBoundsException if the index is invalid + */ + public String substring(final int start) { + return substring(start, size); + } + + /** + * Extracts a portion of this string builder as a string. + *

              + * Note: This method treats an endIndex greater than the length of the + * builder as equal to the length of the builder, and continues + * without error, unlike StringBuffer or String. + * + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @return the new string + * @throws IndexOutOfBoundsException if the index is invalid + */ + public String substring(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + return new String(buffer, startIndex, endIndex - startIndex); + } + + /** + * Extracts the leftmost characters from the string builder without + * throwing an exception. + *

              + * This method extracts the left length characters from + * the builder. If this many characters are not available, the whole + * builder is returned. Thus the returned string may be shorter than the + * length requested. + * + * @param length the number of characters to extract, negative returns empty string + * @return the new string + */ + public String leftString(final int length) { + if (length <= 0) { + return ""; + } else if (length >= size) { + return new String(buffer, 0, size); + } else { + return new String(buffer, 0, length); + } + } + + /** + * Extracts the rightmost characters from the string builder without + * throwing an exception. + *

              + * This method extracts the right length characters from + * the builder. If this many characters are not available, the whole + * builder is returned. Thus the returned string may be shorter than the + * length requested. + * + * @param length the number of characters to extract, negative returns empty string + * @return the new string + */ + public String rightString(final int length) { + if (length <= 0) { + return ""; + } else if (length >= size) { + return new String(buffer, 0, size); + } else { + return new String(buffer, size - length, length); + } + } + + /** + * Extracts some characters from the middle of the string builder without + * throwing an exception. + *

              + * This method extracts length characters from the builder + * at the specified index. + * If the index is negative it is treated as zero. + * If the index is greater than the builder size, it is treated as the builder size. + * If the length is negative, the empty string is returned. + * If insufficient characters are available in the builder, as much as possible is returned. + * Thus the returned string may be shorter than the length requested. + * + * @param index the index to start at, negative means zero + * @param length the number of characters to extract, negative returns empty string + * @return the new string + */ + public String midString(int index, final int length) { + if (index < 0) { + index = 0; + } + if (length <= 0 || index >= size) { + return ""; + } + if (size <= index + length) { + return new String(buffer, index, size - index); + } + return new String(buffer, index, length); + } + + //----------------------------------------------------------------------- + /** + * Checks if the string builder contains the specified char. + * + * @param ch the character to find + * @return true if the builder contains the character + */ + public boolean contains(final char ch) { + final char[] thisBuf = buffer; + for (int i = 0; i < this.size; i++) { + if (thisBuf[i] == ch) { + return true; + } + } + return false; + } + + /** + * Checks if the string builder contains the specified string. + * + * @param str the string to find + * @return true if the builder contains the string + */ + public boolean contains(final String str) { + return indexOf(str, 0) >= 0; + } + + /** + * Checks if the string builder contains a string matched using the + * specified matcher. + *

              + * Matchers can be used to perform advanced searching behaviour. + * For example you could write a matcher to search for the character + * 'a' followed by a number. + * + * @param matcher the matcher to use, null returns -1 + * @return true if the matcher finds a match in the builder + */ + public boolean contains(final StrMatcher matcher) { + return indexOf(matcher, 0) >= 0; + } + + //----------------------------------------------------------------------- + /** + * Searches the string builder to find the first reference to the specified char. + * + * @param ch the character to find + * @return the first index of the character, or -1 if not found + */ + public int indexOf(final char ch) { + return indexOf(ch, 0); + } + + /** + * Searches the string builder to find the first reference to the specified char. + * + * @param ch the character to find + * @param startIndex the index to start at, invalid index rounded to edge + * @return the first index of the character, or -1 if not found + */ + public int indexOf(final char ch, int startIndex) { + startIndex = (startIndex < 0 ? 0 : startIndex); + if (startIndex >= size) { + return -1; + } + final char[] thisBuf = buffer; + for (int i = startIndex; i < size; i++) { + if (thisBuf[i] == ch) { + return i; + } + } + return -1; + } + + /** + * Searches the string builder to find the first reference to the specified string. + *

              + * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @param str the string to find, null returns -1 + * @return the first index of the string, or -1 if not found + */ + public int indexOf(final String str) { + return indexOf(str, 0); + } + + /** + * Searches the string builder to find the first reference to the specified + * string starting searching from the given index. + *

              + * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @param str the string to find, null returns -1 + * @param startIndex the index to start at, invalid index rounded to edge + * @return the first index of the string, or -1 if not found + */ + public int indexOf(final String str, int startIndex) { + startIndex = (startIndex < 0 ? 0 : startIndex); + if (str == null || startIndex >= size) { + return -1; + } + final int strLen = str.length(); + if (strLen == 1) { + return indexOf(str.charAt(0), startIndex); + } + if (strLen == 0) { + return startIndex; + } + if (strLen > size) { + return -1; + } + final char[] thisBuf = buffer; + final int len = size - strLen + 1; + outer: + for (int i = startIndex; i < len; i++) { + for (int j = 0; j < strLen; j++) { + if (str.charAt(j) != thisBuf[i + j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Searches the string builder using the matcher to find the first match. + *

              + * Matchers can be used to perform advanced searching behaviour. + * For example you could write a matcher to find the character 'a' + * followed by a number. + * + * @param matcher the matcher to use, null returns -1 + * @return the first index matched, or -1 if not found + */ + public int indexOf(final StrMatcher matcher) { + return indexOf(matcher, 0); + } + + /** + * Searches the string builder using the matcher to find the first + * match searching from the given index. + *

              + * Matchers can be used to perform advanced searching behaviour. + * For example you could write a matcher to find the character 'a' + * followed by a number. + * + * @param matcher the matcher to use, null returns -1 + * @param startIndex the index to start at, invalid index rounded to edge + * @return the first index matched, or -1 if not found + */ + public int indexOf(final StrMatcher matcher, int startIndex) { + startIndex = (startIndex < 0 ? 0 : startIndex); + if (matcher == null || startIndex >= size) { + return -1; + } + final int len = size; + final char[] buf = buffer; + for (int i = startIndex; i < len; i++) { + if (matcher.isMatch(buf, i, startIndex, len) > 0) { + return i; + } + } + return -1; + } + + //----------------------------------------------------------------------- + /** + * Searches the string builder to find the last reference to the specified char. + * + * @param ch the character to find + * @return the last index of the character, or -1 if not found + */ + public int lastIndexOf(final char ch) { + return lastIndexOf(ch, size - 1); + } + + /** + * Searches the string builder to find the last reference to the specified char. + * + * @param ch the character to find + * @param startIndex the index to start at, invalid index rounded to edge + * @return the last index of the character, or -1 if not found + */ + public int lastIndexOf(final char ch, int startIndex) { + startIndex = (startIndex >= size ? size - 1 : startIndex); + if (startIndex < 0) { + return -1; + } + for (int i = startIndex; i >= 0; i--) { + if (buffer[i] == ch) { + return i; + } + } + return -1; + } + + /** + * Searches the string builder to find the last reference to the specified string. + *

              + * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @param str the string to find, null returns -1 + * @return the last index of the string, or -1 if not found + */ + public int lastIndexOf(final String str) { + return lastIndexOf(str, size - 1); + } + + /** + * Searches the string builder to find the last reference to the specified + * string starting searching from the given index. + *

              + * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @param str the string to find, null returns -1 + * @param startIndex the index to start at, invalid index rounded to edge + * @return the last index of the string, or -1 if not found + */ + public int lastIndexOf(final String str, int startIndex) { + startIndex = (startIndex >= size ? size - 1 : startIndex); + if (str == null || startIndex < 0) { + return -1; + } + final int strLen = str.length(); + if (strLen > 0 && strLen <= size) { + if (strLen == 1) { + return lastIndexOf(str.charAt(0), startIndex); + } + + outer: + for (int i = startIndex - strLen + 1; i >= 0; i--) { + for (int j = 0; j < strLen; j++) { + if (str.charAt(j) != buffer[i + j]) { + continue outer; + } + } + return i; + } + + } else if (strLen == 0) { + return startIndex; + } + return -1; + } + + /** + * Searches the string builder using the matcher to find the last match. + *

              + * Matchers can be used to perform advanced searching behaviour. + * For example you could write a matcher to find the character 'a' + * followed by a number. + * + * @param matcher the matcher to use, null returns -1 + * @return the last index matched, or -1 if not found + */ + public int lastIndexOf(final StrMatcher matcher) { + return lastIndexOf(matcher, size); + } + + /** + * Searches the string builder using the matcher to find the last + * match searching from the given index. + *

              + * Matchers can be used to perform advanced searching behaviour. + * For example you could write a matcher to find the character 'a' + * followed by a number. + * + * @param matcher the matcher to use, null returns -1 + * @param startIndex the index to start at, invalid index rounded to edge + * @return the last index matched, or -1 if not found + */ + public int lastIndexOf(final StrMatcher matcher, int startIndex) { + startIndex = (startIndex >= size ? size - 1 : startIndex); + if (matcher == null || startIndex < 0) { + return -1; + } + final char[] buf = buffer; + final int endIndex = startIndex + 1; + for (int i = startIndex; i >= 0; i--) { + if (matcher.isMatch(buf, i, 0, endIndex) > 0) { + return i; + } + } + return -1; + } + + //----------------------------------------------------------------------- + /** + * Creates a tokenizer that can tokenize the contents of this builder. + *

              + * This method allows the contents of this builder to be tokenized. + * The tokenizer will be setup by default to tokenize on space, tab, + * newline and formfeed (as per StringTokenizer). These values can be + * changed on the tokenizer class, before retrieving the tokens. + *

              + * The returned tokenizer is linked to this builder. You may intermix + * calls to the buider and tokenizer within certain limits, however + * there is no synchronization. Once the tokenizer has been used once, + * it must be {@link StrTokenizer#reset() reset} to pickup the latest + * changes in the builder. For example: + *

              +     * StrBuilder b = new StrBuilder();
              +     * b.append("a b ");
              +     * StrTokenizer t = b.asTokenizer();
              +     * String[] tokens1 = t.getTokenArray();  // returns a,b
              +     * b.append("c d ");
              +     * String[] tokens2 = t.getTokenArray();  // returns a,b (c and d ignored)
              +     * t.reset();              // reset causes builder changes to be picked up
              +     * String[] tokens3 = t.getTokenArray();  // returns a,b,c,d
              +     * 
              + * In addition to simply intermixing appends and tokenization, you can also + * call the set methods on the tokenizer to alter how it tokenizes. Just + * remember to call reset when you want to pickup builder changes. + *

              + * Calling {@link StrTokenizer#reset(String)} or {@link StrTokenizer#reset(char[])} + * with a non-null value will break the link with the builder. + * + * @return a tokenizer that is linked to this builder + */ + public StrTokenizer asTokenizer() { + return new StrBuilderTokenizer(); + } + + //----------------------------------------------------------------------- + /** + * Gets the contents of this builder as a Reader. + *

              + * This method allows the contents of the builder to be read + * using any standard method that expects a Reader. + *

              + * To use, simply create a StrBuilder, populate it with + * data, call asReader, and then read away. + *

              + * The internal character array is shared between the builder and the reader. + * This allows you to append to the builder after creating the reader, + * and the changes will be picked up. + * Note however, that no synchronization occurs, so you must perform + * all operations with the builder and the reader in one thread. + *

              + * The returned reader supports marking, and ignores the flush method. + * + * @return a reader that reads from this builder + */ + public Reader asReader() { + return new StrBuilderReader(); + } + + //----------------------------------------------------------------------- + /** + * Gets this builder as a Writer that can be written to. + *

              + * This method allows you to populate the contents of the builder + * using any standard method that takes a Writer. + *

              + * To use, simply create a StrBuilder, + * call asWriter, and populate away. The data is available + * at any time using the methods of the StrBuilder. + *

              + * The internal character array is shared between the builder and the writer. + * This allows you to intermix calls that append to the builder and + * write using the writer and the changes will be occur correctly. + * Note however, that no synchronization occurs, so you must perform + * all operations with the builder and the writer in one thread. + *

              + * The returned writer ignores the close and flush methods. + * + * @return a writer that populates this builder + */ + public Writer asWriter() { + return new StrBuilderWriter(); + } + + /** + * Appends current contents of this StrBuilder to the + * provided {@link Appendable}. + *

              + * This method tries to avoid doing any extra copies of contents. + * + * @param appendable the appendable to append data to + * @throws IOException if an I/O error occurs + * + * @since 3.4 + * @see #readFrom(Readable) + */ + public void appendTo(final Appendable appendable) throws IOException { + if (appendable instanceof Writer) { + ((Writer) appendable).write(buffer, 0, size); + } else if (appendable instanceof StringBuilder) { + ((StringBuilder) appendable).append(buffer, 0, size); + } else if (appendable instanceof StringBuffer) { + ((StringBuffer) appendable).append(buffer, 0, size); + } else if (appendable instanceof CharBuffer) { + ((CharBuffer) appendable).put(buffer, 0, size); + } else { + appendable.append(this); + } + } + + //----------------------------------------------------------------------- +// /** +// * Gets a String version of the string builder by calling the internal +// * constructor of String by reflection. +// *

              +// * WARNING: You must not use the StrBuilder after calling this method +// * as the buffer is now shared with the String object. To ensure this, +// * the internal character array is set to null, so you will get +// * NullPointerExceptions on all method calls. +// * +// * @return the builder as a String +// */ +// public String toSharedString() { +// try { +// Constructor con = String.class.getDeclaredConstructor( +// new Class[] {int.class, int.class, char[].class}); +// con.setAccessible(true); +// char[] buffer = buf; +// buf = null; +// size = -1; +// nullText = null; +// return (String) con.newInstance( +// new Object[] {Integer.valueOf(0), Integer.valueOf(size), buffer}); +// +// } catch (Exception ex) { +// ex.printStackTrace(); +// throw new UnsupportedOperationException("StrBuilder.toSharedString is unsupported: " + ex.getMessage()); +// } +// } + + //----------------------------------------------------------------------- + /** + * Checks the contents of this builder against another to see if they + * contain the same character content ignoring case. + * + * @param other the object to check, null returns false + * @return true if the builders contain the same characters in the same order + */ + public boolean equalsIgnoreCase(final StrBuilder other) { + if (this == other) { + return true; + } + if (this.size != other.size) { + return false; + } + final char thisBuf[] = this.buffer; + final char otherBuf[] = other.buffer; + for (int i = size - 1; i >= 0; i--) { + final char c1 = thisBuf[i]; + final char c2 = otherBuf[i]; + if (c1 != c2 && Character.toUpperCase(c1) != Character.toUpperCase(c2)) { + return false; + } + } + return true; + } + + /** + * Checks the contents of this builder against another to see if they + * contain the same character content. + * + * @param other the object to check, null returns false + * @return true if the builders contain the same characters in the same order + */ + public boolean equals(final StrBuilder other) { + if (this == other) { + return true; + } + if (this.size != other.size) { + return false; + } + final char thisBuf[] = this.buffer; + final char otherBuf[] = other.buffer; + for (int i = size - 1; i >= 0; i--) { + if (thisBuf[i] != otherBuf[i]) { + return false; + } + } + return true; + } + + /** + * Checks the contents of this builder against another to see if they + * contain the same character content. + * + * @param obj the object to check, null returns false + * @return true if the builders contain the same characters in the same order + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof StrBuilder) { + return equals((StrBuilder) obj); + } + return false; + } + + /** + * Gets a suitable hash code for this builder. + * + * @return a hash code + */ + @Override + public int hashCode() { + final char buf[] = buffer; + int hash = 0; + for (int i = size - 1; i >= 0; i--) { + hash = 31 * hash + buf[i]; + } + return hash; + } + + //----------------------------------------------------------------------- + /** + * Gets a String version of the string builder, creating a new instance + * each time the method is called. + *

              + * Note that unlike StringBuffer, the string version returned is + * independent of the string builder. + * + * @return the builder as a String + */ + @Override + public String toString() { + return new String(buffer, 0, size); + } + + /** + * Gets a StringBuffer version of the string builder, creating a + * new instance each time the method is called. + * + * @return the builder as a StringBuffer + */ + public StringBuffer toStringBuffer() { + return new StringBuffer(size).append(buffer, 0, size); + } + + /** + * Gets a StringBuilder version of the string builder, creating a + * new instance each time the method is called. + * + * @return the builder as a StringBuilder + * @since 3.2 + */ + public StringBuilder toStringBuilder() { + return new StringBuilder(size).append(buffer, 0, size); + } + + /** + * Implement the {@link Builder} interface. + * @return the builder as a String + * @since 3.2 + * @see #toString() + */ + @Override + public String build() { + return toString(); + } + + //----------------------------------------------------------------------- + /** + * Validates parameters defining a range of the builder. + * + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @return the new string + * @throws IndexOutOfBoundsException if the index is invalid + */ + protected int validateRange(final int startIndex, int endIndex) { + if (startIndex < 0) { + throw new StringIndexOutOfBoundsException(startIndex); + } + if (endIndex > size) { + endIndex = size; + } + if (startIndex > endIndex) { + throw new StringIndexOutOfBoundsException("end < start"); + } + return endIndex; + } + + /** + * Validates parameters defining a single index in the builder. + * + * @param index the index, must be valid + * @throws IndexOutOfBoundsException if the index is invalid + */ + protected void validateIndex(final int index) { + if (index < 0 || index > size) { + throw new StringIndexOutOfBoundsException(index); + } + } + + //----------------------------------------------------------------------- + /** + * Inner class to allow StrBuilder to operate as a tokenizer. + */ + class StrBuilderTokenizer extends StrTokenizer { + + /** + * Default constructor. + */ + StrBuilderTokenizer() { + super(); + } + + /** {@inheritDoc} */ + @Override + protected List tokenize(final char[] chars, final int offset, final int count) { + if (chars == null) { + return super.tokenize(StrBuilder.this.buffer, 0, StrBuilder.this.size()); + } + return super.tokenize(chars, offset, count); + } + + /** {@inheritDoc} */ + @Override + public String getContent() { + final String str = super.getContent(); + if (str == null) { + return StrBuilder.this.toString(); + } + return str; + } + } + + //----------------------------------------------------------------------- + /** + * Inner class to allow StrBuilder to operate as a reader. + */ + class StrBuilderReader extends Reader { + /** The current stream position. */ + private int pos; + /** The last mark position. */ + private int mark; + + /** + * Default constructor. + */ + StrBuilderReader() { + super(); + } + + /** {@inheritDoc} */ + @Override + public void close() { + // do nothing + } + + /** {@inheritDoc} */ + @Override + public int read() { + if (ready() == false) { + return -1; + } + return StrBuilder.this.charAt(pos++); + } + + /** {@inheritDoc} */ + @Override + public int read(final char b[], final int off, int len) { + if (off < 0 || len < 0 || off > b.length || + (off + len) > b.length || (off + len) < 0) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return 0; + } + if (pos >= StrBuilder.this.size()) { + return -1; + } + if (pos + len > size()) { + len = StrBuilder.this.size() - pos; + } + StrBuilder.this.getChars(pos, pos + len, b, off); + pos += len; + return len; + } + + /** {@inheritDoc} */ + @Override + public long skip(long n) { + if (pos + n > StrBuilder.this.size()) { + n = StrBuilder.this.size() - pos; + } + if (n < 0) { + return 0; + } + pos += n; + return n; + } + + /** {@inheritDoc} */ + @Override + public boolean ready() { + return pos < StrBuilder.this.size(); + } + + /** {@inheritDoc} */ + @Override + public boolean markSupported() { + return true; + } + + /** {@inheritDoc} */ + @Override + public void mark(final int readAheadLimit) { + mark = pos; + } + + /** {@inheritDoc} */ + @Override + public void reset() { + pos = mark; + } + } + + //----------------------------------------------------------------------- + /** + * Inner class to allow StrBuilder to operate as a writer. + */ + class StrBuilderWriter extends Writer { + + /** + * Default constructor. + */ + StrBuilderWriter() { + super(); + } + + /** {@inheritDoc} */ + @Override + public void close() { + // do nothing + } + + /** {@inheritDoc} */ + @Override + public void flush() { + // do nothing + } + + /** {@inheritDoc} */ + @Override + public void write(final int c) { + StrBuilder.this.append((char) c); + } + + /** {@inheritDoc} */ + @Override + public void write(final char[] cbuf) { + StrBuilder.this.append(cbuf); + } + + /** {@inheritDoc} */ + @Override + public void write(final char[] cbuf, final int off, final int len) { + StrBuilder.this.append(cbuf, off, len); + } + + /** {@inheritDoc} */ + @Override + public void write(final String str) { + StrBuilder.this.append(str); + } + + /** {@inheritDoc} */ + @Override + public void write(final String str, final int off, final int len) { + StrBuilder.this.append(str, off, len); + } + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrLookup.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrLookup.java new file mode 100644 index 000000000..2672b8fd1 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrLookup.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text; + +import java.util.Enumeration; +import java.util.Map; +import java.util.Properties; + +/** + * Lookup a String key to a String value. + *

              + * This class represents the simplest form of a string to string map. + * It has a benefit over a map in that it can create the result on + * demand based on the key. + *

              + * This class comes complete with various factory methods. + * If these do not suffice, you can subclass and implement your own matcher. + *

              + * For example, it would be possible to implement a lookup that used the + * key as a primary key, and looked up the value on demand from the database + * + * @since 2.2 + * @version $Id: StrLookup.java 1654144 2015-01-23 08:47:00Z britter $ + */ +public abstract class StrLookup { + + /** + * Lookup that always returns null. + */ + private static final StrLookup NONE_LOOKUP = new MapStrLookup(null); + + //----------------------------------------------------------------------- + /** + * Returns a lookup which always returns null. + * + * @return a lookup that always returns null, not null + */ + public static StrLookup noneLookup() { + return NONE_LOOKUP; + } + + /** + * Creates a copy of the given properties instance. + * + * @param input the Properties instance to copy. + * @return a copy of {@code input}. + */ + private static Properties copyProperties(Properties input) { + if (input == null) { + return null; + } + + Properties output = new Properties(); + @SuppressWarnings("unchecked") // Property names are Strings. + Enumeration propertyNames = (Enumeration) input.propertyNames(); + + while (propertyNames.hasMoreElements()) { + String propertyName = propertyNames.nextElement(); + output.setProperty(propertyName, input.getProperty(propertyName)); + } + + return output; + } + + /** + * Returns a new lookup which uses a copy of the current + * {@link System#getProperties() System properties}. + *

              + * If a security manager blocked access to system properties, then null will + * be returned from every lookup. + *

              + * If a null key is used, this lookup will throw a NullPointerException. + * + * @return a lookup using system properties, not null + */ + public static StrLookup systemPropertiesLookup() { + Properties systemProperties = null; + + try { + systemProperties = System.getProperties(); + } catch (final SecurityException ex) { + // Squelched. All lookup(String) will return null. + } + + Properties properties = copyProperties(systemProperties); + @SuppressWarnings("unchecked") // System property keys and values are always Strings + final Map propertiesMap = (Map) properties; + + return new MapStrLookup(propertiesMap); + } + + /** + * Returns a lookup which looks up values using a map. + *

              + * If the map is null, then null will be returned from every lookup. + * The map result object is converted to a string using toString(). + * + * @param the type of the values supported by the lookup + * @param map the map of keys to values, may be null + * @return a lookup using the map, not null + */ + public static StrLookup mapLookup(final Map map) { + return new MapStrLookup(map); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + */ + protected StrLookup() { + super(); + } + + /** + * Looks up a String key to a String value. + *

              + * The internal implementation may use any mechanism to return the value. + * The simplest implementation is to use a Map. However, virtually any + * implementation is possible. + *

              + * For example, it would be possible to implement a lookup that used the + * key as a primary key, and looked up the value on demand from the database + * Or, a numeric based implementation could be created that treats the key + * as an integer, increments the value and return the result as a string - + * converting 1 to 2, 15 to 16 etc. + *

              + * The {@link #lookup(String)} method always returns a String, regardless of + * the underlying data, by converting it as necessary. For example: + *

              +     * Map<String, Object> map = new HashMap<String, Object>();
              +     * map.put("number", Integer.valueOf(2));
              +     * assertEquals("2", StrLookup.mapLookup(map).lookup("number"));
              +     * 
              + * @param key the key to be looked up, may be null + * @return the matching value, null if no match + */ + public abstract String lookup(String key); + + //----------------------------------------------------------------------- + /** + * Lookup implementation that uses a Map. + */ + static class MapStrLookup extends StrLookup { + + /** Map keys are variable names and value. */ + private final Map map; + + /** + * Creates a new instance backed by a Map. + * + * @param map the map of keys to values, may be null + */ + MapStrLookup(final Map map) { + this.map = map; + } + + /** + * Looks up a String key to a String value using the map. + *

              + * If the map is null, then null is returned. + * The map result object is converted to a string using toString(). + * + * @param key the key to be looked up, may be null + * @return the matching value, null if no match + */ + @Override + public String lookup(final String key) { + if (map == null) { + return null; + } + final Object obj = map.get(key); + if (obj == null) { + return null; + } + return obj.toString(); + } + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrMatcher.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrMatcher.java new file mode 100644 index 000000000..1836bbb1e --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrMatcher.java @@ -0,0 +1,436 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text; + +import java.util.Arrays; + +import com.fr.third.org.apache.commons.lang3.StringUtils; + +/** + * A matcher class that can be queried to determine if a character array + * portion matches. + *

              + * This class comes complete with various factory methods. + * If these do not suffice, you can subclass and implement your own matcher. + * + * @since 2.2 + * @version $Id: StrMatcher.java 1436770 2013-01-22 07:09:45Z ggregory $ + */ +public abstract class StrMatcher { + + /** + * Matches the comma character. + */ + private static final StrMatcher COMMA_MATCHER = new CharMatcher(','); + /** + * Matches the tab character. + */ + private static final StrMatcher TAB_MATCHER = new CharMatcher('\t'); + /** + * Matches the space character. + */ + private static final StrMatcher SPACE_MATCHER = new CharMatcher(' '); + /** + * Matches the same characters as StringTokenizer, + * namely space, tab, newline, formfeed. + */ + private static final StrMatcher SPLIT_MATCHER = new CharSetMatcher(" \t\n\r\f".toCharArray()); + /** + * Matches the String trim() whitespace characters. + */ + private static final StrMatcher TRIM_MATCHER = new TrimMatcher(); + /** + * Matches the double quote character. + */ + private static final StrMatcher SINGLE_QUOTE_MATCHER = new CharMatcher('\''); + /** + * Matches the double quote character. + */ + private static final StrMatcher DOUBLE_QUOTE_MATCHER = new CharMatcher('"'); + /** + * Matches the single or double quote character. + */ + private static final StrMatcher QUOTE_MATCHER = new CharSetMatcher("'\"".toCharArray()); + /** + * Matches no characters. + */ + private static final StrMatcher NONE_MATCHER = new NoMatcher(); + + // ----------------------------------------------------------------------- + + /** + * Returns a matcher which matches the comma character. + * + * @return a matcher for a comma + */ + public static StrMatcher commaMatcher() { + return COMMA_MATCHER; + } + + /** + * Returns a matcher which matches the tab character. + * + * @return a matcher for a tab + */ + public static StrMatcher tabMatcher() { + return TAB_MATCHER; + } + + /** + * Returns a matcher which matches the space character. + * + * @return a matcher for a space + */ + public static StrMatcher spaceMatcher() { + return SPACE_MATCHER; + } + + /** + * Matches the same characters as StringTokenizer, + * namely space, tab, newline and formfeed. + * + * @return the split matcher + */ + public static StrMatcher splitMatcher() { + return SPLIT_MATCHER; + } + + /** + * Matches the String trim() whitespace characters. + * + * @return the trim matcher + */ + public static StrMatcher trimMatcher() { + return TRIM_MATCHER; + } + + /** + * Returns a matcher which matches the single quote character. + * + * @return a matcher for a single quote + */ + public static StrMatcher singleQuoteMatcher() { + return SINGLE_QUOTE_MATCHER; + } + + /** + * Returns a matcher which matches the double quote character. + * + * @return a matcher for a double quote + */ + public static StrMatcher doubleQuoteMatcher() { + return DOUBLE_QUOTE_MATCHER; + } + + /** + * Returns a matcher which matches the single or double quote character. + * + * @return a matcher for a single or double quote + */ + public static StrMatcher quoteMatcher() { + return QUOTE_MATCHER; + } + + /** + * Matches no characters. + * + * @return a matcher that matches nothing + */ + public static StrMatcher noneMatcher() { + return NONE_MATCHER; + } + + /** + * Constructor that creates a matcher from a character. + * + * @param ch the character to match, must not be null + * @return a new Matcher for the given char + */ + public static StrMatcher charMatcher(final char ch) { + return new CharMatcher(ch); + } + + /** + * Constructor that creates a matcher from a set of characters. + * + * @param chars the characters to match, null or empty matches nothing + * @return a new matcher for the given char[] + */ + public static StrMatcher charSetMatcher(final char... chars) { + if (chars == null || chars.length == 0) { + return NONE_MATCHER; + } + if (chars.length == 1) { + return new CharMatcher(chars[0]); + } + return new CharSetMatcher(chars); + } + + /** + * Constructor that creates a matcher from a string representing a set of characters. + * + * @param chars the characters to match, null or empty matches nothing + * @return a new Matcher for the given characters + */ + public static StrMatcher charSetMatcher(final String chars) { + if (StringUtils.isEmpty(chars)) { + return NONE_MATCHER; + } + if (chars.length() == 1) { + return new CharMatcher(chars.charAt(0)); + } + return new CharSetMatcher(chars.toCharArray()); + } + + /** + * Constructor that creates a matcher from a string. + * + * @param str the string to match, null or empty matches nothing + * @return a new Matcher for the given String + */ + public static StrMatcher stringMatcher(final String str) { + if (StringUtils.isEmpty(str)) { + return NONE_MATCHER; + } + return new StringMatcher(str); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + */ + protected StrMatcher() { + super(); + } + + /** + * Returns the number of matching characters, zero for no match. + *

              + * This method is called to check for a match. + * The parameter pos represents the current position to be + * checked in the string buffer (a character array which must + * not be changed). + * The API guarantees that pos is a valid index for buffer. + *

              + * The character array may be larger than the active area to be matched. + * Only values in the buffer between the specifed indices may be accessed. + *

              + * The matching code may check one character or many. + * It may check characters preceding pos as well as those + * after, so long as no checks exceed the bounds specified. + *

              + * It must return zero for no match, or a positive number if a match was found. + * The number indicates the number of characters that matched. + * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @param bufferStart the first active index in the buffer, valid for buffer + * @param bufferEnd the end index (exclusive) of the active buffer, valid for buffer + * @return the number of matching characters, zero for no match + */ + public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd); + + /** + * Returns the number of matching characters, zero for no match. + *

              + * This method is called to check for a match. + * The parameter pos represents the current position to be + * checked in the string buffer (a character array which must + * not be changed). + * The API guarantees that pos is a valid index for buffer. + *

              + * The matching code may check one character or many. + * It may check characters preceding pos as well as those after. + *

              + * It must return zero for no match, or a positive number if a match was found. + * The number indicates the number of characters that matched. + * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @return the number of matching characters, zero for no match + * @since 2.4 + */ + public int isMatch(final char[] buffer, final int pos) { + return isMatch(buffer, pos, 0, buffer.length); + } + + //----------------------------------------------------------------------- + /** + * Class used to define a set of characters for matching purposes. + */ + static final class CharSetMatcher extends StrMatcher { + /** The set of characters to match. */ + private final char[] chars; + + /** + * Constructor that creates a matcher from a character array. + * + * @param chars the characters to match, must not be null + */ + CharSetMatcher(final char chars[]) { + super(); + this.chars = chars.clone(); + Arrays.sort(this.chars); + } + + /** + * Returns whether or not the given character matches. + * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @param bufferStart the first active index in the buffer, valid for buffer + * @param bufferEnd the end index of the active buffer, valid for buffer + * @return the number of matching characters, zero for no match + */ + @Override + public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) { + return Arrays.binarySearch(chars, buffer[pos]) >= 0 ? 1 : 0; + } + } + + //----------------------------------------------------------------------- + /** + * Class used to define a character for matching purposes. + */ + static final class CharMatcher extends StrMatcher { + /** The character to match. */ + private final char ch; + + /** + * Constructor that creates a matcher that matches a single character. + * + * @param ch the character to match + */ + CharMatcher(final char ch) { + super(); + this.ch = ch; + } + + /** + * Returns whether or not the given character matches. + * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @param bufferStart the first active index in the buffer, valid for buffer + * @param bufferEnd the end index of the active buffer, valid for buffer + * @return the number of matching characters, zero for no match + */ + @Override + public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) { + return ch == buffer[pos] ? 1 : 0; + } + } + + //----------------------------------------------------------------------- + /** + * Class used to define a set of characters for matching purposes. + */ + static final class StringMatcher extends StrMatcher { + /** The string to match, as a character array. */ + private final char[] chars; + + /** + * Constructor that creates a matcher from a String. + * + * @param str the string to match, must not be null + */ + StringMatcher(final String str) { + super(); + chars = str.toCharArray(); + } + + /** + * Returns whether or not the given text matches the stored string. + * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @param bufferStart the first active index in the buffer, valid for buffer + * @param bufferEnd the end index of the active buffer, valid for buffer + * @return the number of matching characters, zero for no match + */ + @Override + public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) { + final int len = chars.length; + if (pos + len > bufferEnd) { + return 0; + } + for (int i = 0; i < chars.length; i++, pos++) { + if (chars[i] != buffer[pos]) { + return 0; + } + } + return len; + } + } + + //----------------------------------------------------------------------- + /** + * Class used to match no characters. + */ + static final class NoMatcher extends StrMatcher { + + /** + * Constructs a new instance of NoMatcher. + */ + NoMatcher() { + super(); + } + + /** + * Always returns false. + * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @param bufferStart the first active index in the buffer, valid for buffer + * @param bufferEnd the end index of the active buffer, valid for buffer + * @return the number of matching characters, zero for no match + */ + @Override + public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) { + return 0; + } + } + + //----------------------------------------------------------------------- + /** + * Class used to match whitespace as per trim(). + */ + static final class TrimMatcher extends StrMatcher { + + /** + * Constructs a new instance of TrimMatcher. + */ + TrimMatcher() { + super(); + } + + /** + * Returns whether or not the given character matches. + * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @param bufferStart the first active index in the buffer, valid for buffer + * @param bufferEnd the end index of the active buffer, valid for buffer + * @return the number of matching characters, zero for no match + */ + @Override + public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) { + return buffer[pos] <= 32 ? 1 : 0; + } + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrSubstitutor.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrSubstitutor.java new file mode 100644 index 000000000..8ecdaf731 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrSubstitutor.java @@ -0,0 +1,1196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import com.fr.third.org.apache.commons.lang3.StringUtils; + +/** + * Substitutes variables within a string by values. + *

              + * This class takes a piece of text and substitutes all the variables within it. + * The default definition of a variable is ${variableName}. + * The prefix and suffix can be changed via constructors and set methods. + *

              + * Variable values are typically resolved from a map, but could also be resolved + * from system properties, or by supplying a custom variable resolver. + *

              + * The simplest example is to use this class to replace Java System properties. For example: + *

              + * StrSubstitutor.replaceSystemProperties(
              + *      "You are running with java.version = ${java.version} and os.name = ${os.name}.");
              + * 
              + *

              + * Typical usage of this class follows the following pattern: First an instance is created + * and initialized with the map that contains the values for the available variables. + * If a prefix and/or suffix for variables should be used other than the default ones, + * the appropriate settings can be performed. After that the replace() + * method can be called passing in the source text for interpolation. In the returned + * text all variable references (as long as their values are known) will be resolved. + * The following example demonstrates this: + *

              + * Map valuesMap = HashMap();
              + * valuesMap.put("animal", "quick brown fox");
              + * valuesMap.put("target", "lazy dog");
              + * String templateString = "The ${animal} jumped over the ${target}.";
              + * StrSubstitutor sub = new StrSubstitutor(valuesMap);
              + * String resolvedString = sub.replace(templateString);
              + * 
              + * yielding: + *
              + *      The quick brown fox jumped over the lazy dog.
              + * 
              + *

              + * Also, this class allows to set a default value for unresolved variables. + * The default value for a variable can be appended to the variable name after the variable + * default value delimiter. The default value of the variable default value delimiter is ':-', + * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated. + * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)}, + * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}. + * The following shows an example with varialbe default value settings: + *

              + * Map valuesMap = HashMap();
              + * valuesMap.put("animal", "quick brown fox");
              + * valuesMap.put("target", "lazy dog");
              + * String templateString = "The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}.";
              + * StrSubstitutor sub = new StrSubstitutor(valuesMap);
              + * String resolvedString = sub.replace(templateString);
              + * 
              + * yielding: + *
              + *      The quick brown fox jumped over the lazy dog. 1234567890.
              + * 
              + *

              + * In addition to this usage pattern there are some static convenience methods that + * cover the most common use cases. These methods can be used without the need of + * manually creating an instance. However if multiple replace operations are to be + * performed, creating and reusing an instance of this class will be more efficient. + *

              + * Variable replacement works in a recursive way. Thus, if a variable value contains + * a variable then that variable will also be replaced. Cyclic replacements are + * detected and will cause an exception to be thrown. + *

              + * Sometimes the interpolation's result must contain a variable prefix. As an example + * take the following source text: + *

              + *   The variable ${${name}} must be used.
              + * 
              + * Here only the variable's name referred to in the text should be replaced resulting + * in the text (assuming that the value of the name variable is x): + *
              + *   The variable ${x} must be used.
              + * 
              + * To achieve this effect there are two possibilities: Either set a different prefix + * and suffix for variables which do not conflict with the result text you want to + * produce. The other possibility is to use the escape character, by default '$'. + * If this character is placed before a variable reference, this reference is ignored + * and won't be replaced. For example: + *
              + *   The variable $${${name}} must be used.
              + * 
              + *

              + * In some complex scenarios you might even want to perform substitution in the + * names of variables, for instance + *

              + * ${jre-${java.specification.version}}
              + * 
              + * StrSubstitutor supports this recursive substitution in variable + * names, but it has to be enabled explicitly by setting the + * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables} + * property to true. + * + * @version $Id: StrSubstitutor.java 1606063 2014-06-27 12:34:37Z ggregory $ + * @since 2.2 + */ +public class StrSubstitutor { + + /** + * Constant for the default escape character. + */ + public static final char DEFAULT_ESCAPE = '$'; + /** + * Constant for the default variable prefix. + */ + public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${"); + /** + * Constant for the default variable suffix. + */ + public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}"); + /** + * Constant for the default value delimiter of a variable. + * @since 3.2 + */ + public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(":-"); + + /** + * Stores the escape character. + */ + private char escapeChar; + /** + * Stores the variable prefix. + */ + private StrMatcher prefixMatcher; + /** + * Stores the variable suffix. + */ + private StrMatcher suffixMatcher; + /** + * Stores the default variable value delimiter + */ + private StrMatcher valueDelimiterMatcher; + /** + * Variable resolution is delegated to an implementor of VariableResolver. + */ + private StrLookup variableResolver; + /** + * The flag whether substitution in variable names is enabled. + */ + private boolean enableSubstitutionInVariables; + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables in the given source object with + * their matching values from the map. + * + * @param the type of the values in the map + * @param source the source text containing the variables to substitute, null returns null + * @param valueMap the map with the values, may be null + * @return the result of the replace operation + */ + public static String replace(final Object source, final Map valueMap) { + return new StrSubstitutor(valueMap).replace(source); + } + + /** + * Replaces all the occurrences of variables in the given source object with + * their matching values from the map. This method allows to specifiy a + * custom variable prefix and suffix + * + * @param the type of the values in the map + * @param source the source text containing the variables to substitute, null returns null + * @param valueMap the map with the values, may be null + * @param prefix the prefix of variables, not null + * @param suffix the suffix of variables, not null + * @return the result of the replace operation + * @throws IllegalArgumentException if the prefix or suffix is null + */ + public static String replace(final Object source, final Map valueMap, final String prefix, final String suffix) { + return new StrSubstitutor(valueMap, prefix, suffix).replace(source); + } + + /** + * Replaces all the occurrences of variables in the given source object with their matching + * values from the properties. + * + * @param source the source text containing the variables to substitute, null returns null + * @param valueProperties the properties with values, may be null + * @return the result of the replace operation + */ + public static String replace(final Object source, final Properties valueProperties) { + if (valueProperties == null) { + return source.toString(); + } + final Map valueMap = new HashMap(); + final Enumeration propNames = valueProperties.propertyNames(); + while (propNames.hasMoreElements()) { + final String propName = (String)propNames.nextElement(); + final String propValue = valueProperties.getProperty(propName); + valueMap.put(propName, propValue); + } + return StrSubstitutor.replace(source, valueMap); + } + + /** + * Replaces all the occurrences of variables in the given source object with + * their matching values from the system properties. + * + * @param source the source text containing the variables to substitute, null returns null + * @return the result of the replace operation + */ + public static String replaceSystemProperties(final Object source) { + return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source); + } + + //----------------------------------------------------------------------- + /** + * Creates a new instance with defaults for variable prefix and suffix + * and the escaping character. + */ + public StrSubstitutor() { + this((StrLookup) null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); + } + + /** + * Creates a new instance and initializes it. Uses defaults for variable + * prefix and suffix and the escaping character. + * + * @param the type of the values in the map + * @param valueMap the map with the variables' values, may be null + */ + public StrSubstitutor(final Map valueMap) { + this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); + } + + /** + * Creates a new instance and initializes it. Uses a default escaping character. + * + * @param the type of the values in the map + * @param valueMap the map with the variables' values, may be null + * @param prefix the prefix for variables, not null + * @param suffix the suffix for variables, not null + * @throws IllegalArgumentException if the prefix or suffix is null + */ + public StrSubstitutor(final Map valueMap, final String prefix, final String suffix) { + this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE); + } + + /** + * Creates a new instance and initializes it. + * + * @param the type of the values in the map + * @param valueMap the map with the variables' values, may be null + * @param prefix the prefix for variables, not null + * @param suffix the suffix for variables, not null + * @param escape the escape character + * @throws IllegalArgumentException if the prefix or suffix is null + */ + public StrSubstitutor(final Map valueMap, final String prefix, final String suffix, + final char escape) { + this(StrLookup.mapLookup(valueMap), prefix, suffix, escape); + } + + /** + * Creates a new instance and initializes it. + * + * @param the type of the values in the map + * @param valueMap the map with the variables' values, may be null + * @param prefix the prefix for variables, not null + * @param suffix the suffix for variables, not null + * @param escape the escape character + * @param valueDelimiter the variable default value delimiter, may be null + * @throws IllegalArgumentException if the prefix or suffix is null + * @since 3.2 + */ + public StrSubstitutor(final Map valueMap, final String prefix, final String suffix, + final char escape, final String valueDelimiter) { + this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter); + } + + /** + * Creates a new instance and initializes it. + * + * @param variableResolver the variable resolver, may be null + */ + public StrSubstitutor(final StrLookup variableResolver) { + this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); + } + + /** + * Creates a new instance and initializes it. + * + * @param variableResolver the variable resolver, may be null + * @param prefix the prefix for variables, not null + * @param suffix the suffix for variables, not null + * @param escape the escape character + * @throws IllegalArgumentException if the prefix or suffix is null + */ + public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, + final char escape) { + this.setVariableResolver(variableResolver); + this.setVariablePrefix(prefix); + this.setVariableSuffix(suffix); + this.setEscapeChar(escape); + this.setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER); + } + + /** + * Creates a new instance and initializes it. + * + * @param variableResolver the variable resolver, may be null + * @param prefix the prefix for variables, not null + * @param suffix the suffix for variables, not null + * @param escape the escape character + * @param valueDelimiter the variable default value delimiter string, may be null + * @throws IllegalArgumentException if the prefix or suffix is null + * @since 3.2 + */ + public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, + final char escape, final String valueDelimiter) { + this.setVariableResolver(variableResolver); + this.setVariablePrefix(prefix); + this.setVariableSuffix(suffix); + this.setEscapeChar(escape); + this.setValueDelimiter(valueDelimiter); + } + + /** + * Creates a new instance and initializes it. + * + * @param variableResolver the variable resolver, may be null + * @param prefixMatcher the prefix for variables, not null + * @param suffixMatcher the suffix for variables, not null + * @param escape the escape character + * @throws IllegalArgumentException if the prefix or suffix is null + */ + public StrSubstitutor( + final StrLookup variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, + final char escape) { + this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER); + } + + /** + * Creates a new instance and initializes it. + * + * @param variableResolver the variable resolver, may be null + * @param prefixMatcher the prefix for variables, not null + * @param suffixMatcher the suffix for variables, not null + * @param escape the escape character + * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null + * @throws IllegalArgumentException if the prefix or suffix is null + * @since 3.2 + */ + public StrSubstitutor( + final StrLookup variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, + final char escape, final StrMatcher valueDelimiterMatcher) { + this.setVariableResolver(variableResolver); + this.setVariablePrefixMatcher(prefixMatcher); + this.setVariableSuffixMatcher(suffixMatcher); + this.setEscapeChar(escape); + this.setValueDelimiterMatcher(valueDelimiterMatcher); + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source string as a template. + * + * @param source the string to replace in, null returns null + * @return the result of the replace operation + */ + public String replace(final String source) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(source); + if (substitute(buf, 0, source.length()) == false) { + return source; + } + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source string as a template. + *

              + * Only the specified portion of the string will be processed. + * The rest of the string is not processed, and is not returned. + * + * @param source the string to replace in, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + */ + public String replace(final String source, final int offset, final int length) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + if (substitute(buf, 0, length) == false) { + return source.substring(offset, offset + length); + } + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source array as a template. + * The array is not altered by this method. + * + * @param source the character array to replace in, not altered, null returns null + * @return the result of the replace operation + */ + public String replace(final char[] source) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(source.length).append(source); + substitute(buf, 0, source.length); + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source array as a template. + * The array is not altered by this method. + *

              + * Only the specified portion of the array will be processed. + * The rest of the array is not processed, and is not returned. + * + * @param source the character array to replace in, not altered, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + */ + public String replace(final char[] source, final int offset, final int length) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + substitute(buf, 0, length); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source buffer as a template. + * The buffer is not altered by this method. + * + * @param source the buffer to use as a template, not changed, null returns null + * @return the result of the replace operation + */ + public String replace(final StringBuffer source) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(source.length()).append(source); + substitute(buf, 0, buf.length()); + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source buffer as a template. + * The buffer is not altered by this method. + *

              + * Only the specified portion of the buffer will be processed. + * The rest of the buffer is not processed, and is not returned. + * + * @param source the buffer to use as a template, not changed, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + */ + public String replace(final StringBuffer source, final int offset, final int length) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + substitute(buf, 0, length); + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source as a template. + * The source is not altered by this method. + * + * @param source the buffer to use as a template, not changed, null returns null + * @return the result of the replace operation + * @since 3.2 + */ + public String replace(final CharSequence source) { + if (source == null) { + return null; + } + return replace(source, 0, source.length()); + } + + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source as a template. + * The source is not altered by this method. + *

              + * Only the specified portion of the buffer will be processed. + * The rest of the buffer is not processed, and is not returned. + * + * @param source the buffer to use as a template, not changed, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + * @since 3.2 + */ + public String replace(final CharSequence source, final int offset, final int length) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + substitute(buf, 0, length); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source builder as a template. + * The builder is not altered by this method. + * + * @param source the builder to use as a template, not changed, null returns null + * @return the result of the replace operation + */ + public String replace(final StrBuilder source) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(source.length()).append(source); + substitute(buf, 0, buf.length()); + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source builder as a template. + * The builder is not altered by this method. + *

              + * Only the specified portion of the builder will be processed. + * The rest of the builder is not processed, and is not returned. + * + * @param source the builder to use as a template, not changed, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + */ + public String replace(final StrBuilder source, final int offset, final int length) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + substitute(buf, 0, length); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables in the given source object with + * their matching values from the resolver. The input source object is + * converted to a string using toString and is not altered. + * + * @param source the source to replace in, null returns null + * @return the result of the replace operation + */ + public String replace(final Object source) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder().append(source); + substitute(buf, 0, buf.length()); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables within the given source buffer + * with their matching values from the resolver. + * The buffer is updated with the result. + * + * @param source the buffer to replace in, updated, null returns zero + * @return true if altered + */ + public boolean replaceIn(final StringBuffer source) { + if (source == null) { + return false; + } + return replaceIn(source, 0, source.length()); + } + + /** + * Replaces all the occurrences of variables within the given source buffer + * with their matching values from the resolver. + * The buffer is updated with the result. + *

              + * Only the specified portion of the buffer will be processed. + * The rest of the buffer is not processed, but it is not deleted. + * + * @param source the buffer to replace in, updated, null returns zero + * @param offset the start offset within the array, must be valid + * @param length the length within the buffer to be processed, must be valid + * @return true if altered + */ + public boolean replaceIn(final StringBuffer source, final int offset, final int length) { + if (source == null) { + return false; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + if (substitute(buf, 0, length) == false) { + return false; + } + source.replace(offset, offset + length, buf.toString()); + return true; + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables within the given source buffer + * with their matching values from the resolver. + * The buffer is updated with the result. + * + * @param source the buffer to replace in, updated, null returns zero + * @return true if altered + * @since 3.2 + */ + public boolean replaceIn(final StringBuilder source) { + if (source == null) { + return false; + } + return replaceIn(source, 0, source.length()); + } + + /** + * Replaces all the occurrences of variables within the given source builder + * with their matching values from the resolver. + * The builder is updated with the result. + *

              + * Only the specified portion of the buffer will be processed. + * The rest of the buffer is not processed, but it is not deleted. + * + * @param source the buffer to replace in, updated, null returns zero + * @param offset the start offset within the array, must be valid + * @param length the length within the buffer to be processed, must be valid + * @return true if altered + * @since 3.2 + */ + public boolean replaceIn(final StringBuilder source, final int offset, final int length) { + if (source == null) { + return false; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + if (substitute(buf, 0, length) == false) { + return false; + } + source.replace(offset, offset + length, buf.toString()); + return true; + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables within the given source + * builder with their matching values from the resolver. + * + * @param source the builder to replace in, updated, null returns zero + * @return true if altered + */ + public boolean replaceIn(final StrBuilder source) { + if (source == null) { + return false; + } + return substitute(source, 0, source.length()); + } + + /** + * Replaces all the occurrences of variables within the given source + * builder with their matching values from the resolver. + *

              + * Only the specified portion of the builder will be processed. + * The rest of the builder is not processed, but it is not deleted. + * + * @param source the builder to replace in, null returns zero + * @param offset the start offset within the array, must be valid + * @param length the length within the builder to be processed, must be valid + * @return true if altered + */ + public boolean replaceIn(final StrBuilder source, final int offset, final int length) { + if (source == null) { + return false; + } + return substitute(source, offset, length); + } + + //----------------------------------------------------------------------- + /** + * Internal method that substitutes the variables. + *

              + * Most users of this class do not need to call this method. This method will + * be called automatically by another (public) method. + *

              + * Writers of subclasses can override this method if they need access to + * the substitution process at the start or end. + * + * @param buf the string builder to substitute into, not null + * @param offset the start offset within the builder, must be valid + * @param length the length within the builder to be processed, must be valid + * @return true if altered + */ + protected boolean substitute(final StrBuilder buf, final int offset, final int length) { + return substitute(buf, offset, length, null) > 0; + } + + /** + * Recursive handler for multiple levels of interpolation. This is the main + * interpolation method, which resolves the values of all variable references + * contained in the passed in text. + * + * @param buf the string builder to substitute into, not null + * @param offset the start offset within the builder, must be valid + * @param length the length within the builder to be processed, must be valid + * @param priorVariables the stack keeping track of the replaced variables, may be null + * @return the length change that occurs, unless priorVariables is null when the int + * represents a boolean flag as to whether any change occurred. + */ + private int substitute(final StrBuilder buf, final int offset, final int length, List priorVariables) { + final StrMatcher pfxMatcher = getVariablePrefixMatcher(); + final StrMatcher suffMatcher = getVariableSuffixMatcher(); + final char escape = getEscapeChar(); + final StrMatcher valueDelimMatcher = getValueDelimiterMatcher(); + final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables(); + + final boolean top = priorVariables == null; + boolean altered = false; + int lengthChange = 0; + char[] chars = buf.buffer; + int bufEnd = offset + length; + int pos = offset; + while (pos < bufEnd) { + final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset, + bufEnd); + if (startMatchLen == 0) { + pos++; + } else { + // found variable start marker + if (pos > offset && chars[pos - 1] == escape) { + // escaped + buf.deleteCharAt(pos - 1); + chars = buf.buffer; // in case buffer was altered + lengthChange--; + altered = true; + bufEnd--; + } else { + // find suffix + final int startPos = pos; + pos += startMatchLen; + int endMatchLen = 0; + int nestedVarCount = 0; + while (pos < bufEnd) { + if (substitutionInVariablesEnabled + && (endMatchLen = pfxMatcher.isMatch(chars, + pos, offset, bufEnd)) != 0) { + // found a nested variable start + nestedVarCount++; + pos += endMatchLen; + continue; + } + + endMatchLen = suffMatcher.isMatch(chars, pos, offset, + bufEnd); + if (endMatchLen == 0) { + pos++; + } else { + // found variable end marker + if (nestedVarCount == 0) { + String varNameExpr = new String(chars, startPos + + startMatchLen, pos - startPos + - startMatchLen); + if (substitutionInVariablesEnabled) { + final StrBuilder bufName = new StrBuilder(varNameExpr); + substitute(bufName, 0, bufName.length()); + varNameExpr = bufName.toString(); + } + pos += endMatchLen; + final int endPos = pos; + + String varName = varNameExpr; + String varDefaultValue = null; + + if (valueDelimMatcher != null) { + final char [] varNameExprChars = varNameExpr.toCharArray(); + int valueDelimiterMatchLen = 0; + for (int i = 0; i < varNameExprChars.length; i++) { + // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value. + if (!substitutionInVariablesEnabled + && pfxMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) { + break; + } + if ((valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i)) != 0) { + varName = varNameExpr.substring(0, i); + varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen); + break; + } + } + } + + // on the first call initialize priorVariables + if (priorVariables == null) { + priorVariables = new ArrayList(); + priorVariables.add(new String(chars, + offset, length)); + } + + // handle cyclic substitution + checkCyclicSubstitution(varName, priorVariables); + priorVariables.add(varName); + + // resolve the variable + String varValue = resolveVariable(varName, buf, + startPos, endPos); + if (varValue == null) { + varValue = varDefaultValue; + } + if (varValue != null) { + // recursive replace + final int varLen = varValue.length(); + buf.replace(startPos, endPos, varValue); + altered = true; + int change = substitute(buf, startPos, + varLen, priorVariables); + change = change + + varLen - (endPos - startPos); + pos += change; + bufEnd += change; + lengthChange += change; + chars = buf.buffer; // in case buffer was + // altered + } + + // remove variable from the cyclic stack + priorVariables + .remove(priorVariables.size() - 1); + break; + } + nestedVarCount--; + pos += endMatchLen; + } + } + } + } + } + if (top) { + return altered ? 1 : 0; + } + return lengthChange; + } + + /** + * Checks if the specified variable is already in the stack (list) of variables. + * + * @param varName the variable name to check + * @param priorVariables the list of prior variables + */ + private void checkCyclicSubstitution(final String varName, final List priorVariables) { + if (priorVariables.contains(varName) == false) { + return; + } + final StrBuilder buf = new StrBuilder(256); + buf.append("Infinite loop in property interpolation of "); + buf.append(priorVariables.remove(0)); + buf.append(": "); + buf.appendWithSeparators(priorVariables, "->"); + throw new IllegalStateException(buf.toString()); + } + + /** + * Internal method that resolves the value of a variable. + *

              + * Most users of this class do not need to call this method. This method is + * called automatically by the substitution process. + *

              + * Writers of subclasses can override this method if they need to alter + * how each substitution occurs. The method is passed the variable's name + * and must return the corresponding value. This implementation uses the + * {@link #getVariableResolver()} with the variable's name as the key. + * + * @param variableName the name of the variable, not null + * @param buf the buffer where the substitution is occurring, not null + * @param startPos the start position of the variable including the prefix, valid + * @param endPos the end position of the variable including the suffix, valid + * @return the variable's value or null if the variable is unknown + */ + protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) { + final StrLookup resolver = getVariableResolver(); + if (resolver == null) { + return null; + } + return resolver.lookup(variableName); + } + + // Escape + //----------------------------------------------------------------------- + /** + * Returns the escape character. + * + * @return the character used for escaping variable references + */ + public char getEscapeChar() { + return this.escapeChar; + } + + /** + * Sets the escape character. + * If this character is placed before a variable reference in the source + * text, this variable will be ignored. + * + * @param escapeCharacter the escape character (0 for disabling escaping) + */ + public void setEscapeChar(final char escapeCharacter) { + this.escapeChar = escapeCharacter; + } + + // Prefix + //----------------------------------------------------------------------- + /** + * Gets the variable prefix matcher currently in use. + *

              + * The variable prefix is the characer or characters that identify the + * start of a variable. This prefix is expressed in terms of a matcher + * allowing advanced prefix matches. + * + * @return the prefix matcher in use + */ + public StrMatcher getVariablePrefixMatcher() { + return prefixMatcher; + } + + /** + * Sets the variable prefix matcher currently in use. + *

              + * The variable prefix is the characer or characters that identify the + * start of a variable. This prefix is expressed in terms of a matcher + * allowing advanced prefix matches. + * + * @param prefixMatcher the prefix matcher to use, null ignored + * @return this, to enable chaining + * @throws IllegalArgumentException if the prefix matcher is null + */ + public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { + if (prefixMatcher == null) { + throw new IllegalArgumentException("Variable prefix matcher must not be null!"); + } + this.prefixMatcher = prefixMatcher; + return this; + } + + /** + * Sets the variable prefix to use. + *

              + * The variable prefix is the character or characters that identify the + * start of a variable. This method allows a single character prefix to + * be easily set. + * + * @param prefix the prefix character to use + * @return this, to enable chaining + */ + public StrSubstitutor setVariablePrefix(final char prefix) { + return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); + } + + /** + * Sets the variable prefix to use. + *

              + * The variable prefix is the characer or characters that identify the + * start of a variable. This method allows a string prefix to be easily set. + * + * @param prefix the prefix for variables, not null + * @return this, to enable chaining + * @throws IllegalArgumentException if the prefix is null + */ + public StrSubstitutor setVariablePrefix(final String prefix) { + if (prefix == null) { + throw new IllegalArgumentException("Variable prefix must not be null!"); + } + return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix)); + } + + // Suffix + //----------------------------------------------------------------------- + /** + * Gets the variable suffix matcher currently in use. + *

              + * The variable suffix is the characer or characters that identify the + * end of a variable. This suffix is expressed in terms of a matcher + * allowing advanced suffix matches. + * + * @return the suffix matcher in use + */ + public StrMatcher getVariableSuffixMatcher() { + return suffixMatcher; + } + + /** + * Sets the variable suffix matcher currently in use. + *

              + * The variable suffix is the characer or characters that identify the + * end of a variable. This suffix is expressed in terms of a matcher + * allowing advanced suffix matches. + * + * @param suffixMatcher the suffix matcher to use, null ignored + * @return this, to enable chaining + * @throws IllegalArgumentException if the suffix matcher is null + */ + public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { + if (suffixMatcher == null) { + throw new IllegalArgumentException("Variable suffix matcher must not be null!"); + } + this.suffixMatcher = suffixMatcher; + return this; + } + + /** + * Sets the variable suffix to use. + *

              + * The variable suffix is the characer or characters that identify the + * end of a variable. This method allows a single character suffix to + * be easily set. + * + * @param suffix the suffix character to use + * @return this, to enable chaining + */ + public StrSubstitutor setVariableSuffix(final char suffix) { + return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); + } + + /** + * Sets the variable suffix to use. + *

              + * The variable suffix is the character or characters that identify the + * end of a variable. This method allows a string suffix to be easily set. + * + * @param suffix the suffix for variables, not null + * @return this, to enable chaining + * @throws IllegalArgumentException if the suffix is null + */ + public StrSubstitutor setVariableSuffix(final String suffix) { + if (suffix == null) { + throw new IllegalArgumentException("Variable suffix must not be null!"); + } + return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix)); + } + + // Variable Default Value Delimiter + //----------------------------------------------------------------------- + /** + * Gets the variable default value delimiter matcher currently in use. + *

              + * The variable default value delimiter is the characer or characters that delimite the + * variable name and the variable default value. This delimiter is expressed in terms of a matcher + * allowing advanced variable default value delimiter matches. + *

              + * If it returns null, then the variable default value resolution is disabled. + * + * @return the variable default value delimiter matcher in use, may be null + * @since 3.2 + */ + public StrMatcher getValueDelimiterMatcher() { + return valueDelimiterMatcher; + } + + /** + * Sets the variable default value delimiter matcher to use. + *

              + * The variable default value delimiter is the characer or characters that delimite the + * variable name and the variable default value. This delimiter is expressed in terms of a matcher + * allowing advanced variable default value delimiter matches. + *

              + * If the valueDelimiterMatcher is null, then the variable default value resolution + * becomes disabled. + * + * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null + * @return this, to enable chaining + * @since 3.2 + */ + public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { + this.valueDelimiterMatcher = valueDelimiterMatcher; + return this; + } + + /** + * Sets the variable default value delimiter to use. + *

              + * The variable default value delimiter is the characer or characters that delimite the + * variable name and the variable default value. This method allows a single character + * variable default value delimiter to be easily set. + * + * @param valueDelimiter the variable default value delimiter character to use + * @return this, to enable chaining + * @since 3.2 + */ + public StrSubstitutor setValueDelimiter(final char valueDelimiter) { + return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter)); + } + + /** + * Sets the variable default value delimiter to use. + *

              + * The variable default value delimiter is the characer or characters that delimite the + * variable name and the variable default value. This method allows a string + * variable default value delimiter to be easily set. + *

              + * If the valueDelimiter is null or empty string, then the variable default + * value resolution becomes disabled. + * + * @param valueDelimiter the variable default value delimiter string to use, may be null or empty + * @return this, to enable chaining + * @since 3.2 + */ + public StrSubstitutor setValueDelimiter(final String valueDelimiter) { + if (StringUtils.isEmpty(valueDelimiter)) { + setValueDelimiterMatcher(null); + return this; + } + return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter)); + } + + // Resolver + //----------------------------------------------------------------------- + /** + * Gets the VariableResolver that is used to lookup variables. + * + * @return the VariableResolver + */ + public StrLookup getVariableResolver() { + return this.variableResolver; + } + + /** + * Sets the VariableResolver that is used to lookup variables. + * + * @param variableResolver the VariableResolver + */ + public void setVariableResolver(final StrLookup variableResolver) { + this.variableResolver = variableResolver; + } + + // Substitution support in variable names + //----------------------------------------------------------------------- + /** + * Returns a flag whether substitution is done in variable names. + * + * @return the substitution in variable names flag + * @since 3.0 + */ + public boolean isEnableSubstitutionInVariables() { + return enableSubstitutionInVariables; + } + + /** + * Sets a flag whether substitution is done in variable names. If set to + * true, the names of variables can contain other variables which are + * processed first before the original variable is evaluated, e.g. + * ${jre-${java.version}}. The default value is false. + * + * @param enableSubstitutionInVariables the new value of the flag + * @since 3.0 + */ + public void setEnableSubstitutionInVariables( + final boolean enableSubstitutionInVariables) { + this.enableSubstitutionInVariables = enableSubstitutionInVariables; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrTokenizer.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrTokenizer.java new file mode 100644 index 000000000..790e7125f --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/StrTokenizer.java @@ -0,0 +1,1114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; +import com.fr.third.org.apache.commons.lang3.StringUtils; + +/** + * Tokenizes a string based based on delimiters (separators) + * and supporting quoting and ignored character concepts. + *

              + * This class can split a String into many smaller strings. It aims + * to do a similar job to {@link java.util.StringTokenizer StringTokenizer}, + * however it offers much more control and flexibility including implementing + * the ListIterator interface. By default, it is set up + * like StringTokenizer. + *

              + * The input String is split into a number of tokens. + * Each token is separated from the next String by a delimiter. + * One or more delimiter characters must be specified. + *

              + * Each token may be surrounded by quotes. + * The quote matcher specifies the quote character(s). + * A quote may be escaped within a quoted section by duplicating itself. + *

              + * Between each token and the delimiter are potentially characters that need trimming. + * The trimmer matcher specifies these characters. + * One usage might be to trim whitespace characters. + *

              + * At any point outside the quotes there might potentially be invalid characters. + * The ignored matcher specifies these characters to be removed. + * One usage might be to remove new line characters. + *

              + * Empty tokens may be removed or returned as null. + *

              + * "a,b,c"         - Three tokens "a","b","c"   (comma delimiter)
              + * " a, b , c "    - Three tokens "a","b","c"   (default CSV processing trims whitespace)
              + * "a, ", b ,", c" - Three tokens "a, " , " b ", ", c" (quoted text untouched)
              + * 
              + *

              + * + * This tokenizer has the following properties and options: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
              PropertyTypeDefault
              delimCharSetMatcher{ \t\n\r\f}
              quoteNoneMatcher{}
              ignoreNoneMatcher{}
              emptyTokenAsNullbooleanfalse
              ignoreEmptyTokensbooleantrue
              + * + * @since 2.2 + * @version $Id: StrTokenizer.java 1583482 2014-03-31 22:54:57Z niallp $ + */ +public class StrTokenizer implements ListIterator, Cloneable { + + private static final StrTokenizer CSV_TOKENIZER_PROTOTYPE; + private static final StrTokenizer TSV_TOKENIZER_PROTOTYPE; + static { + CSV_TOKENIZER_PROTOTYPE = new StrTokenizer(); + CSV_TOKENIZER_PROTOTYPE.setDelimiterMatcher(StrMatcher.commaMatcher()); + CSV_TOKENIZER_PROTOTYPE.setQuoteMatcher(StrMatcher.doubleQuoteMatcher()); + CSV_TOKENIZER_PROTOTYPE.setIgnoredMatcher(StrMatcher.noneMatcher()); + CSV_TOKENIZER_PROTOTYPE.setTrimmerMatcher(StrMatcher.trimMatcher()); + CSV_TOKENIZER_PROTOTYPE.setEmptyTokenAsNull(false); + CSV_TOKENIZER_PROTOTYPE.setIgnoreEmptyTokens(false); + + TSV_TOKENIZER_PROTOTYPE = new StrTokenizer(); + TSV_TOKENIZER_PROTOTYPE.setDelimiterMatcher(StrMatcher.tabMatcher()); + TSV_TOKENIZER_PROTOTYPE.setQuoteMatcher(StrMatcher.doubleQuoteMatcher()); + TSV_TOKENIZER_PROTOTYPE.setIgnoredMatcher(StrMatcher.noneMatcher()); + TSV_TOKENIZER_PROTOTYPE.setTrimmerMatcher(StrMatcher.trimMatcher()); + TSV_TOKENIZER_PROTOTYPE.setEmptyTokenAsNull(false); + TSV_TOKENIZER_PROTOTYPE.setIgnoreEmptyTokens(false); + } + + /** The text to work on. */ + private char chars[]; + /** The parsed tokens */ + private String tokens[]; + /** The current iteration position */ + private int tokenPos; + + /** The delimiter matcher */ + private StrMatcher delimMatcher = StrMatcher.splitMatcher(); + /** The quote matcher */ + private StrMatcher quoteMatcher = StrMatcher.noneMatcher(); + /** The ignored matcher */ + private StrMatcher ignoredMatcher = StrMatcher.noneMatcher(); + /** The trimmer matcher */ + private StrMatcher trimmerMatcher = StrMatcher.noneMatcher(); + + /** Whether to return empty tokens as null */ + private boolean emptyAsNull = false; + /** Whether to ignore empty tokens */ + private boolean ignoreEmptyTokens = true; + + //----------------------------------------------------------------------- + + /** + * Returns a clone of CSV_TOKENIZER_PROTOTYPE. + * + * @return a clone of CSV_TOKENIZER_PROTOTYPE. + */ + private static StrTokenizer getCSVClone() { + return (StrTokenizer) CSV_TOKENIZER_PROTOTYPE.clone(); + } + + /** + * Gets a new tokenizer instance which parses Comma Separated Value strings + * initializing it with the given input. The default for CSV processing + * will be trim whitespace from both ends (which can be overridden with + * the setTrimmer method). + *

              + * You must call a "reset" method to set the string which you want to parse. + * @return a new tokenizer instance which parses Comma Separated Value strings + */ + public static StrTokenizer getCSVInstance() { + return getCSVClone(); + } + + /** + * Gets a new tokenizer instance which parses Comma Separated Value strings + * initializing it with the given input. The default for CSV processing + * will be trim whitespace from both ends (which can be overridden with + * the setTrimmer method). + * + * @param input the text to parse + * @return a new tokenizer instance which parses Comma Separated Value strings + */ + public static StrTokenizer getCSVInstance(final String input) { + final StrTokenizer tok = getCSVClone(); + tok.reset(input); + return tok; + } + + /** + * Gets a new tokenizer instance which parses Comma Separated Value strings + * initializing it with the given input. The default for CSV processing + * will be trim whitespace from both ends (which can be overridden with + * the setTrimmer method). + * + * @param input the text to parse + * @return a new tokenizer instance which parses Comma Separated Value strings + */ + public static StrTokenizer getCSVInstance(final char[] input) { + final StrTokenizer tok = getCSVClone(); + tok.reset(input); + return tok; + } + + /** + * Returns a clone of TSV_TOKENIZER_PROTOTYPE. + * + * @return a clone of TSV_TOKENIZER_PROTOTYPE. + */ + private static StrTokenizer getTSVClone() { + return (StrTokenizer) TSV_TOKENIZER_PROTOTYPE.clone(); + } + + + /** + * Gets a new tokenizer instance which parses Tab Separated Value strings. + * The default for CSV processing will be trim whitespace from both ends + * (which can be overridden with the setTrimmer method). + *

              + * You must call a "reset" method to set the string which you want to parse. + * @return a new tokenizer instance which parses Tab Separated Value strings. + */ + public static StrTokenizer getTSVInstance() { + return getTSVClone(); + } + + /** + * Gets a new tokenizer instance which parses Tab Separated Value strings. + * The default for CSV processing will be trim whitespace from both ends + * (which can be overridden with the setTrimmer method). + * @param input the string to parse + * @return a new tokenizer instance which parses Tab Separated Value strings. + */ + public static StrTokenizer getTSVInstance(final String input) { + final StrTokenizer tok = getTSVClone(); + tok.reset(input); + return tok; + } + + /** + * Gets a new tokenizer instance which parses Tab Separated Value strings. + * The default for CSV processing will be trim whitespace from both ends + * (which can be overridden with the setTrimmer method). + * @param input the string to parse + * @return a new tokenizer instance which parses Tab Separated Value strings. + */ + public static StrTokenizer getTSVInstance(final char[] input) { + final StrTokenizer tok = getTSVClone(); + tok.reset(input); + return tok; + } + + //----------------------------------------------------------------------- + /** + * Constructs a tokenizer splitting on space, tab, newline and formfeed + * as per StringTokenizer, but with no text to tokenize. + *

              + * This constructor is normally used with {@link #reset(String)}. + */ + public StrTokenizer() { + super(); + this.chars = null; + } + + /** + * Constructs a tokenizer splitting on space, tab, newline and formfeed + * as per StringTokenizer. + * + * @param input the string which is to be parsed + */ + public StrTokenizer(final String input) { + super(); + if (input != null) { + chars = input.toCharArray(); + } else { + chars = null; + } + } + + /** + * Constructs a tokenizer splitting on the specified delimiter character. + * + * @param input the string which is to be parsed + * @param delim the field delimiter character + */ + public StrTokenizer(final String input, final char delim) { + this(input); + setDelimiterChar(delim); + } + + /** + * Constructs a tokenizer splitting on the specified delimiter string. + * + * @param input the string which is to be parsed + * @param delim the field delimiter string + */ + public StrTokenizer(final String input, final String delim) { + this(input); + setDelimiterString(delim); + } + + /** + * Constructs a tokenizer splitting using the specified delimiter matcher. + * + * @param input the string which is to be parsed + * @param delim the field delimiter matcher + */ + public StrTokenizer(final String input, final StrMatcher delim) { + this(input); + setDelimiterMatcher(delim); + } + + /** + * Constructs a tokenizer splitting on the specified delimiter character + * and handling quotes using the specified quote character. + * + * @param input the string which is to be parsed + * @param delim the field delimiter character + * @param quote the field quoted string character + */ + public StrTokenizer(final String input, final char delim, final char quote) { + this(input, delim); + setQuoteChar(quote); + } + + /** + * Constructs a tokenizer splitting using the specified delimiter matcher + * and handling quotes using the specified quote matcher. + * + * @param input the string which is to be parsed + * @param delim the field delimiter matcher + * @param quote the field quoted string matcher + */ + public StrTokenizer(final String input, final StrMatcher delim, final StrMatcher quote) { + this(input, delim); + setQuoteMatcher(quote); + } + + /** + * Constructs a tokenizer splitting on space, tab, newline and formfeed + * as per StringTokenizer. + * + * @param input the string which is to be parsed, not cloned + */ + public StrTokenizer(final char[] input) { + super(); + this.chars = ArrayUtils.clone(input); + } + + /** + * Constructs a tokenizer splitting on the specified character. + * + * @param input the string which is to be parsed, not cloned + * @param delim the field delimiter character + */ + public StrTokenizer(final char[] input, final char delim) { + this(input); + setDelimiterChar(delim); + } + + /** + * Constructs a tokenizer splitting on the specified string. + * + * @param input the string which is to be parsed, not cloned + * @param delim the field delimiter string + */ + public StrTokenizer(final char[] input, final String delim) { + this(input); + setDelimiterString(delim); + } + + /** + * Constructs a tokenizer splitting using the specified delimiter matcher. + * + * @param input the string which is to be parsed, not cloned + * @param delim the field delimiter matcher + */ + public StrTokenizer(final char[] input, final StrMatcher delim) { + this(input); + setDelimiterMatcher(delim); + } + + /** + * Constructs a tokenizer splitting on the specified delimiter character + * and handling quotes using the specified quote character. + * + * @param input the string which is to be parsed, not cloned + * @param delim the field delimiter character + * @param quote the field quoted string character + */ + public StrTokenizer(final char[] input, final char delim, final char quote) { + this(input, delim); + setQuoteChar(quote); + } + + /** + * Constructs a tokenizer splitting using the specified delimiter matcher + * and handling quotes using the specified quote matcher. + * + * @param input the string which is to be parsed, not cloned + * @param delim the field delimiter character + * @param quote the field quoted string character + */ + public StrTokenizer(final char[] input, final StrMatcher delim, final StrMatcher quote) { + this(input, delim); + setQuoteMatcher(quote); + } + + // API + //----------------------------------------------------------------------- + /** + * Gets the number of tokens found in the String. + * + * @return the number of matched tokens + */ + public int size() { + checkTokenized(); + return tokens.length; + } + + /** + * Gets the next token from the String. + * Equivalent to {@link #next()} except it returns null rather than + * throwing {@link NoSuchElementException} when no tokens remain. + * + * @return the next sequential token, or null when no more tokens are found + */ + public String nextToken() { + if (hasNext()) { + return tokens[tokenPos++]; + } + return null; + } + + /** + * Gets the previous token from the String. + * + * @return the previous sequential token, or null when no more tokens are found + */ + public String previousToken() { + if (hasPrevious()) { + return tokens[--tokenPos]; + } + return null; + } + + /** + * Gets a copy of the full token list as an independent modifiable array. + * + * @return the tokens as a String array + */ + public String[] getTokenArray() { + checkTokenized(); + return tokens.clone(); + } + + /** + * Gets a copy of the full token list as an independent modifiable list. + * + * @return the tokens as a String array + */ + public List getTokenList() { + checkTokenized(); + final List list = new ArrayList(tokens.length); + for (final String element : tokens) { + list.add(element); + } + return list; + } + + /** + * Resets this tokenizer, forgetting all parsing and iteration already completed. + *

              + * This method allows the same tokenizer to be reused for the same String. + * + * @return this, to enable chaining + */ + public StrTokenizer reset() { + tokenPos = 0; + tokens = null; + return this; + } + + /** + * Reset this tokenizer, giving it a new input string to parse. + * In this manner you can re-use a tokenizer with the same settings + * on multiple input lines. + * + * @param input the new string to tokenize, null sets no text to parse + * @return this, to enable chaining + */ + public StrTokenizer reset(final String input) { + reset(); + if (input != null) { + this.chars = input.toCharArray(); + } else { + this.chars = null; + } + return this; + } + + /** + * Reset this tokenizer, giving it a new input string to parse. + * In this manner you can re-use a tokenizer with the same settings + * on multiple input lines. + * + * @param input the new character array to tokenize, not cloned, null sets no text to parse + * @return this, to enable chaining + */ + public StrTokenizer reset(final char[] input) { + reset(); + this.chars = ArrayUtils.clone(input); + return this; + } + + // ListIterator + //----------------------------------------------------------------------- + /** + * Checks whether there are any more tokens. + * + * @return true if there are more tokens + */ + @Override + public boolean hasNext() { + checkTokenized(); + return tokenPos < tokens.length; + } + + /** + * Gets the next token. + * + * @return the next String token + * @throws NoSuchElementException if there are no more elements + */ + @Override + public String next() { + if (hasNext()) { + return tokens[tokenPos++]; + } + throw new NoSuchElementException(); + } + + /** + * Gets the index of the next token to return. + * + * @return the next token index + */ + @Override + public int nextIndex() { + return tokenPos; + } + + /** + * Checks whether there are any previous tokens that can be iterated to. + * + * @return true if there are previous tokens + */ + @Override + public boolean hasPrevious() { + checkTokenized(); + return tokenPos > 0; + } + + /** + * Gets the token previous to the last returned token. + * + * @return the previous token + */ + @Override + public String previous() { + if (hasPrevious()) { + return tokens[--tokenPos]; + } + throw new NoSuchElementException(); + } + + /** + * Gets the index of the previous token. + * + * @return the previous token index + */ + @Override + public int previousIndex() { + return tokenPos - 1; + } + + /** + * Unsupported ListIterator operation. + * + * @throws UnsupportedOperationException always + */ + @Override + public void remove() { + throw new UnsupportedOperationException("remove() is unsupported"); + } + + /** + * Unsupported ListIterator operation. + * @param obj this parameter ignored. + * @throws UnsupportedOperationException always + */ + @Override + public void set(final String obj) { + throw new UnsupportedOperationException("set() is unsupported"); + } + + /** + * Unsupported ListIterator operation. + * @param obj this parameter ignored. + * @throws UnsupportedOperationException always + */ + @Override + public void add(final String obj) { + throw new UnsupportedOperationException("add() is unsupported"); + } + + // Implementation + //----------------------------------------------------------------------- + /** + * Checks if tokenization has been done, and if not then do it. + */ + private void checkTokenized() { + if (tokens == null) { + if (chars == null) { + // still call tokenize as subclass may do some work + final List split = tokenize(null, 0, 0); + tokens = split.toArray(new String[split.size()]); + } else { + final List split = tokenize(chars, 0, chars.length); + tokens = split.toArray(new String[split.size()]); + } + } + } + + /** + * Internal method to performs the tokenization. + *

              + * Most users of this class do not need to call this method. This method + * will be called automatically by other (public) methods when required. + *

              + * This method exists to allow subclasses to add code before or after the + * tokenization. For example, a subclass could alter the character array, + * offset or count to be parsed, or call the tokenizer multiple times on + * multiple strings. It is also be possible to filter the results. + *

              + * StrTokenizer will always pass a zero offset and a count + * equal to the length of the array to this method, however a subclass + * may pass other values, or even an entirely different array. + * + * @param srcChars the character array being tokenized, may be null + * @param offset the start position within the character array, must be valid + * @param count the number of characters to tokenize, must be valid + * @return the modifiable list of String tokens, unmodifiable if null array or zero count + */ + protected List tokenize(final char[] srcChars, final int offset, final int count) { + if (srcChars == null || count == 0) { + return Collections.emptyList(); + } + final StrBuilder buf = new StrBuilder(); + final List tokenList = new ArrayList(); + int pos = offset; + + // loop around the entire buffer + while (pos >= 0 && pos < count) { + // find next token + pos = readNextToken(srcChars, pos, count, buf, tokenList); + + // handle case where end of string is a delimiter + if (pos >= count) { + addToken(tokenList, ""); + } + } + return tokenList; + } + + /** + * Adds a token to a list, paying attention to the parameters we've set. + * + * @param list the list to add to + * @param tok the token to add + */ + private void addToken(final List list, String tok) { + if (StringUtils.isEmpty(tok)) { + if (isIgnoreEmptyTokens()) { + return; + } + if (isEmptyTokenAsNull()) { + tok = null; + } + } + list.add(tok); + } + + /** + * Reads character by character through the String to get the next token. + * + * @param srcChars the character array being tokenized + * @param start the first character of field + * @param len the length of the character array being tokenized + * @param workArea a temporary work area + * @param tokenList the list of parsed tokens + * @return the starting position of the next field (the character + * immediately after the delimiter), or -1 if end of string found + */ + private int readNextToken(final char[] srcChars, int start, final int len, final StrBuilder workArea, final List tokenList) { + // skip all leading whitespace, unless it is the + // field delimiter or the quote character + while (start < len) { + final int removeLen = Math.max( + getIgnoredMatcher().isMatch(srcChars, start, start, len), + getTrimmerMatcher().isMatch(srcChars, start, start, len)); + if (removeLen == 0 || + getDelimiterMatcher().isMatch(srcChars, start, start, len) > 0 || + getQuoteMatcher().isMatch(srcChars, start, start, len) > 0) { + break; + } + start += removeLen; + } + + // handle reaching end + if (start >= len) { + addToken(tokenList, ""); + return -1; + } + + // handle empty token + final int delimLen = getDelimiterMatcher().isMatch(srcChars, start, start, len); + if (delimLen > 0) { + addToken(tokenList, ""); + return start + delimLen; + } + + // handle found token + final int quoteLen = getQuoteMatcher().isMatch(srcChars, start, start, len); + if (quoteLen > 0) { + return readWithQuotes(srcChars, start + quoteLen, len, workArea, tokenList, start, quoteLen); + } + return readWithQuotes(srcChars, start, len, workArea, tokenList, 0, 0); + } + + /** + * Reads a possibly quoted string token. + * + * @param srcChars the character array being tokenized + * @param start the first character of field + * @param len the length of the character array being tokenized + * @param workArea a temporary work area + * @param tokenList the list of parsed tokens + * @param quoteStart the start position of the matched quote, 0 if no quoting + * @param quoteLen the length of the matched quote, 0 if no quoting + * @return the starting position of the next field (the character + * immediately after the delimiter, or if end of string found, + * then the length of string + */ + private int readWithQuotes(final char[] srcChars, final int start, final int len, final StrBuilder workArea, + final List tokenList, final int quoteStart, final int quoteLen) { + // Loop until we've found the end of the quoted + // string or the end of the input + workArea.clear(); + int pos = start; + boolean quoting = quoteLen > 0; + int trimStart = 0; + + while (pos < len) { + // quoting mode can occur several times throughout a string + // we must switch between quoting and non-quoting until we + // encounter a non-quoted delimiter, or end of string + if (quoting) { + // In quoting mode + + // If we've found a quote character, see if it's + // followed by a second quote. If so, then we need + // to actually put the quote character into the token + // rather than end the token. + if (isQuote(srcChars, pos, len, quoteStart, quoteLen)) { + if (isQuote(srcChars, pos + quoteLen, len, quoteStart, quoteLen)) { + // matched pair of quotes, thus an escaped quote + workArea.append(srcChars, pos, quoteLen); + pos += quoteLen * 2; + trimStart = workArea.size(); + continue; + } + + // end of quoting + quoting = false; + pos += quoteLen; + continue; + } + + // copy regular character from inside quotes + workArea.append(srcChars[pos++]); + trimStart = workArea.size(); + + } else { + // Not in quoting mode + + // check for delimiter, and thus end of token + final int delimLen = getDelimiterMatcher().isMatch(srcChars, pos, start, len); + if (delimLen > 0) { + // return condition when end of token found + addToken(tokenList, workArea.substring(0, trimStart)); + return pos + delimLen; + } + + // check for quote, and thus back into quoting mode + if (quoteLen > 0 && isQuote(srcChars, pos, len, quoteStart, quoteLen)) { + quoting = true; + pos += quoteLen; + continue; + } + + // check for ignored (outside quotes), and ignore + final int ignoredLen = getIgnoredMatcher().isMatch(srcChars, pos, start, len); + if (ignoredLen > 0) { + pos += ignoredLen; + continue; + } + + // check for trimmed character + // don't yet know if its at the end, so copy to workArea + // use trimStart to keep track of trim at the end + final int trimmedLen = getTrimmerMatcher().isMatch(srcChars, pos, start, len); + if (trimmedLen > 0) { + workArea.append(srcChars, pos, trimmedLen); + pos += trimmedLen; + continue; + } + + // copy regular character from outside quotes + workArea.append(srcChars[pos++]); + trimStart = workArea.size(); + } + } + + // return condition when end of string found + addToken(tokenList, workArea.substring(0, trimStart)); + return -1; + } + + /** + * Checks if the characters at the index specified match the quote + * already matched in readNextToken(). + * + * @param srcChars the character array being tokenized + * @param pos the position to check for a quote + * @param len the length of the character array being tokenized + * @param quoteStart the start position of the matched quote, 0 if no quoting + * @param quoteLen the length of the matched quote, 0 if no quoting + * @return true if a quote is matched + */ + private boolean isQuote(final char[] srcChars, final int pos, final int len, final int quoteStart, final int quoteLen) { + for (int i = 0; i < quoteLen; i++) { + if (pos + i >= len || srcChars[pos + i] != srcChars[quoteStart + i]) { + return false; + } + } + return true; + } + + // Delimiter + //----------------------------------------------------------------------- + /** + * Gets the field delimiter matcher. + * + * @return the delimiter matcher in use + */ + public StrMatcher getDelimiterMatcher() { + return this.delimMatcher; + } + + /** + * Sets the field delimiter matcher. + *

              + * The delimitier is used to separate one token from another. + * + * @param delim the delimiter matcher to use + * @return this, to enable chaining + */ + public StrTokenizer setDelimiterMatcher(final StrMatcher delim) { + if (delim == null) { + this.delimMatcher = StrMatcher.noneMatcher(); + } else { + this.delimMatcher = delim; + } + return this; + } + + /** + * Sets the field delimiter character. + * + * @param delim the delimiter character to use + * @return this, to enable chaining + */ + public StrTokenizer setDelimiterChar(final char delim) { + return setDelimiterMatcher(StrMatcher.charMatcher(delim)); + } + + /** + * Sets the field delimiter string. + * + * @param delim the delimiter string to use + * @return this, to enable chaining + */ + public StrTokenizer setDelimiterString(final String delim) { + return setDelimiterMatcher(StrMatcher.stringMatcher(delim)); + } + + // Quote + //----------------------------------------------------------------------- + /** + * Gets the quote matcher currently in use. + *

              + * The quote character is used to wrap data between the tokens. + * This enables delimiters to be entered as data. + * The default value is '"' (double quote). + * + * @return the quote matcher in use + */ + public StrMatcher getQuoteMatcher() { + return quoteMatcher; + } + + /** + * Set the quote matcher to use. + *

              + * The quote character is used to wrap data between the tokens. + * This enables delimiters to be entered as data. + * + * @param quote the quote matcher to use, null ignored + * @return this, to enable chaining + */ + public StrTokenizer setQuoteMatcher(final StrMatcher quote) { + if (quote != null) { + this.quoteMatcher = quote; + } + return this; + } + + /** + * Sets the quote character to use. + *

              + * The quote character is used to wrap data between the tokens. + * This enables delimiters to be entered as data. + * + * @param quote the quote character to use + * @return this, to enable chaining + */ + public StrTokenizer setQuoteChar(final char quote) { + return setQuoteMatcher(StrMatcher.charMatcher(quote)); + } + + // Ignored + //----------------------------------------------------------------------- + /** + * Gets the ignored character matcher. + *

              + * These characters are ignored when parsing the String, unless they are + * within a quoted region. + * The default value is not to ignore anything. + * + * @return the ignored matcher in use + */ + public StrMatcher getIgnoredMatcher() { + return ignoredMatcher; + } + + /** + * Set the matcher for characters to ignore. + *

              + * These characters are ignored when parsing the String, unless they are + * within a quoted region. + * + * @param ignored the ignored matcher to use, null ignored + * @return this, to enable chaining + */ + public StrTokenizer setIgnoredMatcher(final StrMatcher ignored) { + if (ignored != null) { + this.ignoredMatcher = ignored; + } + return this; + } + + /** + * Set the character to ignore. + *

              + * This character is ignored when parsing the String, unless it is + * within a quoted region. + * + * @param ignored the ignored character to use + * @return this, to enable chaining + */ + public StrTokenizer setIgnoredChar(final char ignored) { + return setIgnoredMatcher(StrMatcher.charMatcher(ignored)); + } + + // Trimmer + //----------------------------------------------------------------------- + /** + * Gets the trimmer character matcher. + *

              + * These characters are trimmed off on each side of the delimiter + * until the token or quote is found. + * The default value is not to trim anything. + * + * @return the trimmer matcher in use + */ + public StrMatcher getTrimmerMatcher() { + return trimmerMatcher; + } + + /** + * Sets the matcher for characters to trim. + *

              + * These characters are trimmed off on each side of the delimiter + * until the token or quote is found. + * + * @param trimmer the trimmer matcher to use, null ignored + * @return this, to enable chaining + */ + public StrTokenizer setTrimmerMatcher(final StrMatcher trimmer) { + if (trimmer != null) { + this.trimmerMatcher = trimmer; + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets whether the tokenizer currently returns empty tokens as null. + * The default for this property is false. + * + * @return true if empty tokens are returned as null + */ + public boolean isEmptyTokenAsNull() { + return this.emptyAsNull; + } + + /** + * Sets whether the tokenizer should return empty tokens as null. + * The default for this property is false. + * + * @param emptyAsNull whether empty tokens are returned as null + * @return this, to enable chaining + */ + public StrTokenizer setEmptyTokenAsNull(final boolean emptyAsNull) { + this.emptyAsNull = emptyAsNull; + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets whether the tokenizer currently ignores empty tokens. + * The default for this property is true. + * + * @return true if empty tokens are not returned + */ + public boolean isIgnoreEmptyTokens() { + return ignoreEmptyTokens; + } + + /** + * Sets whether the tokenizer should ignore and not return empty tokens. + * The default for this property is true. + * + * @param ignoreEmptyTokens whether empty tokens are not returned + * @return this, to enable chaining + */ + public StrTokenizer setIgnoreEmptyTokens(final boolean ignoreEmptyTokens) { + this.ignoreEmptyTokens = ignoreEmptyTokens; + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the String content that the tokenizer is parsing. + * + * @return the string content being parsed + */ + public String getContent() { + if (chars == null) { + return null; + } + return new String(chars); + } + + //----------------------------------------------------------------------- + /** + * Creates a new instance of this Tokenizer. The new instance is reset so + * that it will be at the start of the token list. + * If a {@link CloneNotSupportedException} is caught, return null. + * + * @return a new instance of this Tokenizer which has been reset. + */ + @Override + public Object clone() { + try { + return cloneReset(); + } catch (final CloneNotSupportedException ex) { + return null; + } + } + + /** + * Creates a new instance of this Tokenizer. The new instance is reset so that + * it will be at the start of the token list. + * + * @return a new instance of this Tokenizer which has been reset. + * @throws CloneNotSupportedException if there is a problem cloning + */ + Object cloneReset() throws CloneNotSupportedException { + // this method exists to enable 100% test coverage + final StrTokenizer cloned = (StrTokenizer) super.clone(); + if (cloned.chars != null) { + cloned.chars = cloned.chars.clone(); + } + cloned.reset(); + return cloned; + } + + //----------------------------------------------------------------------- + /** + * Gets the String content that the tokenizer is parsing. + * + * @return the string content being parsed + */ + @Override + public String toString() { + if (tokens == null) { + return "StrTokenizer[not tokenized yet]"; + } + return "StrTokenizer" + getTokenList(); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/WordUtils.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/WordUtils.java new file mode 100644 index 000000000..bcf9a6865 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/WordUtils.java @@ -0,0 +1,585 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text; + +import com.fr.third.org.apache.commons.lang3.StringUtils; +import com.fr.third.org.apache.commons.lang3.SystemUtils; + +/** + *

              Operations on Strings that contain words.

              + * + *

              This class tries to handle null input gracefully. + * An exception will not be thrown for a null input. + * Each method documents its behaviour in more detail.

              + * + * @since 2.0 + * @version $Id: WordUtils.java 1586649 2014-04-11 13:28:30Z britter $ + */ +public class WordUtils { + + /** + *

              WordUtils instances should NOT be constructed in + * standard programming. Instead, the class should be used as + * WordUtils.wrap("foo bar", 20);.

              + * + *

              This constructor is public to permit tools that require a JavaBean + * instance to operate.

              + */ + public WordUtils() { + super(); + } + + // Wrapping + //-------------------------------------------------------------------------- + /** + *

              Wraps a single line of text, identifying words by ' '.

              + * + *

              New lines will be separated by the system property line separator. + * Very long words, such as URLs will not be wrapped.

              + * + *

              Leading spaces on a new line are stripped. + * Trailing spaces are not stripped.

              + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
              inputwrapLengthresult
              null*null
              ""*""
              "Here is one line of text that is going to be wrapped after 20 columns."20"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."
              "Click here to jump to the commons website - http://commons.apache.org"20"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"
              "Click here, http://commons.apache.org, to jump to the commons website"20"Click here,\nhttp://commons.apache.org,\nto jump to the\ncommons website"
              + * + * (assuming that '\n' is the systems line separator) + * + * @param str the String to be word wrapped, may be null + * @param wrapLength the column to wrap the words at, less than 1 is treated as 1 + * @return a line with newlines inserted, null if null input + */ + public static String wrap(final String str, final int wrapLength) { + return wrap(str, wrapLength, null, false); + } + + /** + *

              Wraps a single line of text, identifying words by ' '.

              + * + *

              Leading spaces on a new line are stripped. + * Trailing spaces are not stripped.

              + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
              inputwrapLenghtnewLineStringwrapLongWordsresult
              null**true/falsenull
              ""**true/false""
              "Here is one line of text that is going to be wrapped after 20 columns."20"\n"true/false"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."
              "Here is one line of text that is going to be wrapped after 20 columns."20"<br />"true/false"Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns."
              "Here is one line of text that is going to be wrapped after 20 columns."20nulltrue/false"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."
              "Click here to jump to the commons website - http://commons.apache.org"20"\n"false"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"
              "Click here to jump to the commons website - http://commons.apache.org"20"\n"true"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"
              + * + * @param str the String to be word wrapped, may be null + * @param wrapLength the column to wrap the words at, less than 1 is treated as 1 + * @param newLineStr the string to insert for a new line, + * null uses the system property line separator + * @param wrapLongWords true if long words (such as URLs) should be wrapped + * @return a line with newlines inserted, null if null input + */ + public static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords) { + if (str == null) { + return null; + } + if (newLineStr == null) { + newLineStr = SystemUtils.LINE_SEPARATOR; + } + if (wrapLength < 1) { + wrapLength = 1; + } + final int inputLineLength = str.length(); + int offset = 0; + final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32); + + while (offset < inputLineLength) { + if (str.charAt(offset) == ' ') { + offset++; + continue; + } + // only last line without leading spaces is left + if(inputLineLength - offset <= wrapLength) { + break; + } + int spaceToWrapAt = str.lastIndexOf(' ', wrapLength + offset); + + if (spaceToWrapAt >= offset) { + // normal case + wrappedLine.append(str.substring(offset, spaceToWrapAt)); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + + } else { + // really long word or URL + if (wrapLongWords) { + // wrap really long word one line at a time + wrappedLine.append(str.substring(offset, wrapLength + offset)); + wrappedLine.append(newLineStr); + offset += wrapLength; + } else { + // do not wrap really long word, just extend beyond limit + spaceToWrapAt = str.indexOf(' ', wrapLength + offset); + if (spaceToWrapAt >= 0) { + wrappedLine.append(str.substring(offset, spaceToWrapAt)); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + } else { + wrappedLine.append(str.substring(offset)); + offset = inputLineLength; + } + } + } + } + + // Whatever is left in line is short enough to just pass through + wrappedLine.append(str.substring(offset)); + + return wrappedLine.toString(); + } + + // Capitalizing + //----------------------------------------------------------------------- + /** + *

              Capitalizes all the whitespace separated words in a String. + * Only the first letter of each word is changed. To convert the + * rest of each word to lowercase at the same time, + * use {@link #capitalizeFully(String)}.

              + * + *

              Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

              + * + *
              +     * WordUtils.capitalize(null)        = null
              +     * WordUtils.capitalize("")          = ""
              +     * WordUtils.capitalize("i am FINE") = "I Am FINE"
              +     * 
              + * + * @param str the String to capitalize, may be null + * @return capitalized String, null if null String input + * @see #uncapitalize(String) + * @see #capitalizeFully(String) + */ + public static String capitalize(final String str) { + return capitalize(str, null); + } + + /** + *

              Capitalizes all the delimiter separated words in a String. + * Only the first letter of each word is changed. To convert the + * rest of each word to lowercase at the same time, + * use {@link #capitalizeFully(String, char[])}.

              + * + *

              The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be capitalized.

              + * + *

              A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

              + * + *
              +     * WordUtils.capitalize(null, *)            = null
              +     * WordUtils.capitalize("", *)              = ""
              +     * WordUtils.capitalize(*, new char[0])     = *
              +     * WordUtils.capitalize("i am fine", null)  = "I Am Fine"
              +     * WordUtils.capitalize("i aM.fine", {'.'}) = "I aM.Fine"
              +     * 
              + * + * @param str the String to capitalize, may be null + * @param delimiters set of characters to determine capitalization, null means whitespace + * @return capitalized String, null if null String input + * @see #uncapitalize(String) + * @see #capitalizeFully(String) + * @since 2.1 + */ + public static String capitalize(final String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + final char[] buffer = str.toCharArray(); + boolean capitalizeNext = true; + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (isDelimiter(ch, delimiters)) { + capitalizeNext = true; + } else if (capitalizeNext) { + buffer[i] = Character.toTitleCase(ch); + capitalizeNext = false; + } + } + return new String(buffer); + } + + //----------------------------------------------------------------------- + /** + *

              Converts all the whitespace separated words in a String into capitalized words, + * that is each word is made up of a titlecase character and then a series of + * lowercase characters.

              + * + *

              Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

              + * + *
              +     * WordUtils.capitalizeFully(null)        = null
              +     * WordUtils.capitalizeFully("")          = ""
              +     * WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
              +     * 
              + * + * @param str the String to capitalize, may be null + * @return capitalized String, null if null String input + */ + public static String capitalizeFully(final String str) { + return capitalizeFully(str, null); + } + + /** + *

              Converts all the delimiter separated words in a String into capitalized words, + * that is each word is made up of a titlecase character and then a series of + * lowercase characters.

              + * + *

              The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be capitalized.

              + * + *

              A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

              + * + *
              +     * WordUtils.capitalizeFully(null, *)            = null
              +     * WordUtils.capitalizeFully("", *)              = ""
              +     * WordUtils.capitalizeFully(*, null)            = *
              +     * WordUtils.capitalizeFully(*, new char[0])     = *
              +     * WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
              +     * 
              + * + * @param str the String to capitalize, may be null + * @param delimiters set of characters to determine capitalization, null means whitespace + * @return capitalized String, null if null String input + * @since 2.1 + */ + public static String capitalizeFully(String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + str = str.toLowerCase(); + return capitalize(str, delimiters); + } + + //----------------------------------------------------------------------- + /** + *

              Uncapitalizes all the whitespace separated words in a String. + * Only the first letter of each word is changed.

              + * + *

              Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null.

              + * + *
              +     * WordUtils.uncapitalize(null)        = null
              +     * WordUtils.uncapitalize("")          = ""
              +     * WordUtils.uncapitalize("I Am FINE") = "i am fINE"
              +     * 
              + * + * @param str the String to uncapitalize, may be null + * @return uncapitalized String, null if null String input + * @see #capitalize(String) + */ + public static String uncapitalize(final String str) { + return uncapitalize(str, null); + } + + /** + *

              Uncapitalizes all the whitespace separated words in a String. + * Only the first letter of each word is changed.

              + * + *

              The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be uncapitalized.

              + * + *

              Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null.

              + * + *
              +     * WordUtils.uncapitalize(null, *)            = null
              +     * WordUtils.uncapitalize("", *)              = ""
              +     * WordUtils.uncapitalize(*, null)            = *
              +     * WordUtils.uncapitalize(*, new char[0])     = *
              +     * WordUtils.uncapitalize("I AM.FINE", {'.'}) = "i AM.fINE"
              +     * 
              + * + * @param str the String to uncapitalize, may be null + * @param delimiters set of characters to determine uncapitalization, null means whitespace + * @return uncapitalized String, null if null String input + * @see #capitalize(String) + * @since 2.1 + */ + public static String uncapitalize(final String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + final char[] buffer = str.toCharArray(); + boolean uncapitalizeNext = true; + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (isDelimiter(ch, delimiters)) { + uncapitalizeNext = true; + } else if (uncapitalizeNext) { + buffer[i] = Character.toLowerCase(ch); + uncapitalizeNext = false; + } + } + return new String(buffer); + } + + //----------------------------------------------------------------------- + /** + *

              Swaps the case of a String using a word based algorithm.

              + * + *
                + *
              • Upper case character converts to Lower case
              • + *
              • Title case character converts to Lower case
              • + *
              • Lower case character after Whitespace or at start converts to Title case
              • + *
              • Other Lower case character converts to Upper case
              • + *
              + * + *

              Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null.

              + * + *
              +     * StringUtils.swapCase(null)                 = null
              +     * StringUtils.swapCase("")                   = ""
              +     * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
              +     * 
              + * + * @param str the String to swap case, may be null + * @return the changed String, null if null String input + */ + public static String swapCase(final String str) { + if (StringUtils.isEmpty(str)) { + return str; + } + final char[] buffer = str.toCharArray(); + + boolean whitespace = true; + + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (Character.isUpperCase(ch)) { + buffer[i] = Character.toLowerCase(ch); + whitespace = false; + } else if (Character.isTitleCase(ch)) { + buffer[i] = Character.toLowerCase(ch); + whitespace = false; + } else if (Character.isLowerCase(ch)) { + if (whitespace) { + buffer[i] = Character.toTitleCase(ch); + whitespace = false; + } else { + buffer[i] = Character.toUpperCase(ch); + } + } else { + whitespace = Character.isWhitespace(ch); + } + } + return new String(buffer); + } + + //----------------------------------------------------------------------- + /** + *

              Extracts the initial letters from each word in the String.

              + * + *

              The first letter of the string and all first letters after + * whitespace are returned as a new string. + * Their case is not changed.

              + * + *

              Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null.

              + * + *
              +     * WordUtils.initials(null)             = null
              +     * WordUtils.initials("")               = ""
              +     * WordUtils.initials("Ben John Lee")   = "BJL"
              +     * WordUtils.initials("Ben J.Lee")      = "BJ"
              +     * 
              + * + * @param str the String to get initials from, may be null + * @return String of initial letters, null if null String input + * @see #initials(String,char[]) + * @since 2.2 + */ + public static String initials(final String str) { + return initials(str, null); + } + + /** + *

              Extracts the initial letters from each word in the String.

              + * + *

              The first letter of the string and all first letters after the + * defined delimiters are returned as a new string. + * Their case is not changed.

              + * + *

              If the delimiters array is null, then Whitespace is used. + * Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null. + * An empty delimiter array returns an empty String.

              + * + *
              +     * WordUtils.initials(null, *)                = null
              +     * WordUtils.initials("", *)                  = ""
              +     * WordUtils.initials("Ben John Lee", null)   = "BJL"
              +     * WordUtils.initials("Ben J.Lee", null)      = "BJ"
              +     * WordUtils.initials("Ben J.Lee", [' ','.']) = "BJL"
              +     * WordUtils.initials(*, new char[0])         = ""
              +     * 
              + * + * @param str the String to get initials from, may be null + * @param delimiters set of characters to determine words, null means whitespace + * @return String of initial letters, null if null String input + * @see #initials(String) + * @since 2.2 + */ + public static String initials(final String str, final char... delimiters) { + if (StringUtils.isEmpty(str)) { + return str; + } + if (delimiters != null && delimiters.length == 0) { + return ""; + } + final int strLen = str.length(); + final char[] buf = new char[strLen / 2 + 1]; + int count = 0; + boolean lastWasGap = true; + for (int i = 0; i < strLen; i++) { + final char ch = str.charAt(i); + + if (isDelimiter(ch, delimiters)) { + lastWasGap = true; + } else if (lastWasGap) { + buf[count++] = ch; + lastWasGap = false; + } else { + continue; // ignore ch + } + } + return new String(buf, 0, count); + } + + //----------------------------------------------------------------------- + /** + * Is the character a delimiter. + * + * @param ch the character to check + * @param delimiters the delimiters + * @return true if it is a delimiter + */ + private static boolean isDelimiter(final char ch, final char[] delimiters) { + if (delimiters == null) { + return Character.isWhitespace(ch); + } + for (final char delimiter : delimiters) { + if (ch == delimiter) { + return true; + } + } + return false; + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/package-info.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/package-info.java new file mode 100644 index 000000000..389626872 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/package-info.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *

              Provides classes for handling and manipulating text, partly as an extension to {@link java.text}. + * The classes in this package are, for the most part, intended to be instantiated (i.e. they are not utility classes + * with lots of static methods).

              + * + *

              Amongst other classes, the text package provides a replacement for {@link java.lang.StringBuffer} named {@link com.fr.third.org.apache.commons.lang3.text.StrBuilder}, a class for substituting variables within a String named {@link com.fr.third.org.apache.commons.lang3.text.StrSubstitutor} and a replacement for {@link java.util.StringTokenizer} named {@link com.fr.third.org.apache.commons.lang3.text.StrTokenizer}. + * While somewhat ungainly, the Str prefix has been used to ensure we don't clash with any current or future standard Java classes.

              + * + * @since 2.1 + * @version $Id: package-info.java 1559146 2014-01-17 15:23:19Z britter $ + */ +package com.fr.third.org.apache.commons.lang3.text; diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/AggregateTranslator.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/AggregateTranslator.java new file mode 100644 index 000000000..52da3a2fd --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/AggregateTranslator.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text.translate; + +import java.io.IOException; +import java.io.Writer; + +import com.fr.third.org.apache.commons.lang3.ArrayUtils; + +/** + * Executes a sequence of translators one after the other. Execution ends whenever + * the first translator consumes codepoints from the input. + * + * @since 3.0 + * @version $Id: AggregateTranslator.java 1436770 2013-01-22 07:09:45Z ggregory $ + */ +public class AggregateTranslator extends CharSequenceTranslator { + + private final CharSequenceTranslator[] translators; + + /** + * Specify the translators to be used at creation time. + * + * @param translators CharSequenceTranslator array to aggregate + */ + public AggregateTranslator(final CharSequenceTranslator... translators) { + this.translators = ArrayUtils.clone(translators); + } + + /** + * The first translator to consume codepoints from the input is the 'winner'. + * Execution stops with the number of consumed codepoints being returned. + * {@inheritDoc} + */ + @Override + public int translate(final CharSequence input, final int index, final Writer out) throws IOException { + for (final CharSequenceTranslator translator : translators) { + final int consumed = translator.translate(input, index, out); + if(consumed != 0) { + return consumed; + } + } + return 0; + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java new file mode 100644 index 000000000..503811e8b --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text.translate; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Locale; + +/** + * An API for translating text. + * Its core use is to escape and unescape text. Because escaping and unescaping + * is completely contextual, the API does not present two separate signatures. + * + * @since 3.0 + * @version $Id: CharSequenceTranslator.java 1666535 2015-03-13 18:18:59Z britter $ + */ +public abstract class CharSequenceTranslator { + + static final char[] HEX_DIGITS = new char[] {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; + + /** + * Translate a set of codepoints, represented by an int index into a CharSequence, + * into another set of codepoints. The number of codepoints consumed must be returned, + * and the only IOExceptions thrown must be from interacting with the Writer so that + * the top level API may reliably ignore StringWriter IOExceptions. + * + * @param input CharSequence that is being translated + * @param index int representing the current point of translation + * @param out Writer to translate the text to + * @return int count of codepoints consumed + * @throws IOException if and only if the Writer produces an IOException + */ + public abstract int translate(CharSequence input, int index, Writer out) throws IOException; + + /** + * Helper for non-Writer usage. + * @param input CharSequence to be translated + * @return String output of translation + */ + public final String translate(final CharSequence input) { + if (input == null) { + return null; + } + try { + final StringWriter writer = new StringWriter(input.length() * 2); + translate(input, writer); + return writer.toString(); + } catch (final IOException ioe) { + // this should never ever happen while writing to a StringWriter + throw new RuntimeException(ioe); + } + } + + /** + * Translate an input onto a Writer. This is intentionally final as its algorithm is + * tightly coupled with the abstract method of this class. + * + * @param input CharSequence that is being translated + * @param out Writer to translate the text to + * @throws IOException if and only if the Writer produces an IOException + */ + public final void translate(final CharSequence input, final Writer out) throws IOException { + if (out == null) { + throw new IllegalArgumentException("The Writer must not be null"); + } + if (input == null) { + return; + } + int pos = 0; + final int len = input.length(); + while (pos < len) { + final int consumed = translate(input, pos, out); + if (consumed == 0) { + // inlined implementation of Character.toChars(Character.codePointAt(input, pos)) + // avoids allocating temp char arrays and duplicate checks + char c1 = input.charAt(pos); + out.write(c1); + pos++; + if (Character.isHighSurrogate(c1) && pos < len) { + char c2 = input.charAt(pos); + if (Character.isLowSurrogate(c2)) { + out.write(c2); + pos++; + } + } + continue; + } + // contract with translators is that they have to understand codepoints + // and they just took care of a surrogate pair + for (int pt = 0; pt < consumed; pt++) { + pos += Character.charCount(Character.codePointAt(input, pos)); + } + } + } + + /** + * Helper method to create a merger of this translator with another set of + * translators. Useful in customizing the standard functionality. + * + * @param translators CharSequenceTranslator array of translators to merge with this one + * @return CharSequenceTranslator merging this translator with the others + */ + public final CharSequenceTranslator with(final CharSequenceTranslator... translators) { + final CharSequenceTranslator[] newArray = new CharSequenceTranslator[translators.length + 1]; + newArray[0] = this; + System.arraycopy(translators, 0, newArray, 1, translators.length); + return new AggregateTranslator(newArray); + } + + /** + *

              Returns an upper case hexadecimal String for the given + * character.

              + * + * @param codepoint The codepoint to convert. + * @return An upper case hexadecimal String + */ + public static String hex(final int codepoint) { + return Integer.toHexString(codepoint).toUpperCase(Locale.ENGLISH); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/CodePointTranslator.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/CodePointTranslator.java new file mode 100644 index 000000000..945f7891a --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/CodePointTranslator.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text.translate; + +import java.io.IOException; +import java.io.Writer; + +/** + * Helper subclass to CharSequenceTranslator to allow for translations that + * will replace up to one character at a time. + * + * @since 3.0 + * @version $Id: CodePointTranslator.java 1553931 2013-12-28 21:24:44Z ggregory $ + */ +public abstract class CodePointTranslator extends CharSequenceTranslator { + + /** + * Implementation of translate that maps onto the abstract translate(int, Writer) method. + * {@inheritDoc} + */ + @Override + public final int translate(final CharSequence input, final int index, final Writer out) throws IOException { + final int codepoint = Character.codePointAt(input, index); + final boolean consumed = translate(codepoint, out); + return consumed ? 1 : 0; + } + + /** + * Translate the specified codepoint into another. + * + * @param codepoint int character input to translate + * @param out Writer to optionally push the translated output to + * @return boolean as to whether translation occurred or not + * @throws IOException if and only if the Writer produces an IOException + */ + public abstract boolean translate(int codepoint, Writer out) throws IOException; + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/EntityArrays.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/EntityArrays.java new file mode 100644 index 000000000..ec8673f75 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/EntityArrays.java @@ -0,0 +1,425 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text.translate; + +/** + * Class holding various entity data for HTML and XML - generally for use with + * the LookupTranslator. + * All arrays are of length [*][2]. + * + * @since 3.0 + * @version $Id: EntityArrays.java 1436770 2013-01-22 07:09:45Z ggregory $ + */ +public class EntityArrays { + + /** + * Mapping to escape ISO-8859-1 + * characters to their named HTML 3.x equivalents. + * @return the mapping table + */ + public static String[][] ISO8859_1_ESCAPE() { return ISO8859_1_ESCAPE.clone(); } + private static final String[][] ISO8859_1_ESCAPE = { + {"\u00A0", " "}, // non-breaking space + {"\u00A1", "¡"}, // inverted exclamation mark + {"\u00A2", "¢"}, // cent sign + {"\u00A3", "£"}, // pound sign + {"\u00A4", "¤"}, // currency sign + {"\u00A5", "¥"}, // yen sign = yuan sign + {"\u00A6", "¦"}, // broken bar = broken vertical bar + {"\u00A7", "§"}, // section sign + {"\u00A8", "¨"}, // diaeresis = spacing diaeresis + {"\u00A9", "©"}, // � - copyright sign + {"\u00AA", "ª"}, // feminine ordinal indicator + {"\u00AB", "«"}, // left-pointing double angle quotation mark = left pointing guillemet + {"\u00AC", "¬"}, // not sign + {"\u00AD", "­"}, // soft hyphen = discretionary hyphen + {"\u00AE", "®"}, // � - registered trademark sign + {"\u00AF", "¯"}, // macron = spacing macron = overline = APL overbar + {"\u00B0", "°"}, // degree sign + {"\u00B1", "±"}, // plus-minus sign = plus-or-minus sign + {"\u00B2", "²"}, // superscript two = superscript digit two = squared + {"\u00B3", "³"}, // superscript three = superscript digit three = cubed + {"\u00B4", "´"}, // acute accent = spacing acute + {"\u00B5", "µ"}, // micro sign + {"\u00B6", "¶"}, // pilcrow sign = paragraph sign + {"\u00B7", "·"}, // middle dot = Georgian comma = Greek middle dot + {"\u00B8", "¸"}, // cedilla = spacing cedilla + {"\u00B9", "¹"}, // superscript one = superscript digit one + {"\u00BA", "º"}, // masculine ordinal indicator + {"\u00BB", "»"}, // right-pointing double angle quotation mark = right pointing guillemet + {"\u00BC", "¼"}, // vulgar fraction one quarter = fraction one quarter + {"\u00BD", "½"}, // vulgar fraction one half = fraction one half + {"\u00BE", "¾"}, // vulgar fraction three quarters = fraction three quarters + {"\u00BF", "¿"}, // inverted question mark = turned question mark + {"\u00C0", "À"}, // � - uppercase A, grave accent + {"\u00C1", "Á"}, // � - uppercase A, acute accent + {"\u00C2", "Â"}, // � - uppercase A, circumflex accent + {"\u00C3", "Ã"}, // � - uppercase A, tilde + {"\u00C4", "Ä"}, // � - uppercase A, umlaut + {"\u00C5", "Å"}, // � - uppercase A, ring + {"\u00C6", "Æ"}, // � - uppercase AE + {"\u00C7", "Ç"}, // � - uppercase C, cedilla + {"\u00C8", "È"}, // � - uppercase E, grave accent + {"\u00C9", "É"}, // � - uppercase E, acute accent + {"\u00CA", "Ê"}, // � - uppercase E, circumflex accent + {"\u00CB", "Ë"}, // � - uppercase E, umlaut + {"\u00CC", "Ì"}, // � - uppercase I, grave accent + {"\u00CD", "Í"}, // � - uppercase I, acute accent + {"\u00CE", "Î"}, // � - uppercase I, circumflex accent + {"\u00CF", "Ï"}, // � - uppercase I, umlaut + {"\u00D0", "Ð"}, // � - uppercase Eth, Icelandic + {"\u00D1", "Ñ"}, // � - uppercase N, tilde + {"\u00D2", "Ò"}, // � - uppercase O, grave accent + {"\u00D3", "Ó"}, // � - uppercase O, acute accent + {"\u00D4", "Ô"}, // � - uppercase O, circumflex accent + {"\u00D5", "Õ"}, // � - uppercase O, tilde + {"\u00D6", "Ö"}, // � - uppercase O, umlaut + {"\u00D7", "×"}, // multiplication sign + {"\u00D8", "Ø"}, // � - uppercase O, slash + {"\u00D9", "Ù"}, // � - uppercase U, grave accent + {"\u00DA", "Ú"}, // � - uppercase U, acute accent + {"\u00DB", "Û"}, // � - uppercase U, circumflex accent + {"\u00DC", "Ü"}, // � - uppercase U, umlaut + {"\u00DD", "Ý"}, // � - uppercase Y, acute accent + {"\u00DE", "Þ"}, // � - uppercase THORN, Icelandic + {"\u00DF", "ß"}, // � - lowercase sharps, German + {"\u00E0", "à"}, // � - lowercase a, grave accent + {"\u00E1", "á"}, // � - lowercase a, acute accent + {"\u00E2", "â"}, // � - lowercase a, circumflex accent + {"\u00E3", "ã"}, // � - lowercase a, tilde + {"\u00E4", "ä"}, // � - lowercase a, umlaut + {"\u00E5", "å"}, // � - lowercase a, ring + {"\u00E6", "æ"}, // � - lowercase ae + {"\u00E7", "ç"}, // � - lowercase c, cedilla + {"\u00E8", "è"}, // � - lowercase e, grave accent + {"\u00E9", "é"}, // � - lowercase e, acute accent + {"\u00EA", "ê"}, // � - lowercase e, circumflex accent + {"\u00EB", "ë"}, // � - lowercase e, umlaut + {"\u00EC", "ì"}, // � - lowercase i, grave accent + {"\u00ED", "í"}, // � - lowercase i, acute accent + {"\u00EE", "î"}, // � - lowercase i, circumflex accent + {"\u00EF", "ï"}, // � - lowercase i, umlaut + {"\u00F0", "ð"}, // � - lowercase eth, Icelandic + {"\u00F1", "ñ"}, // � - lowercase n, tilde + {"\u00F2", "ò"}, // � - lowercase o, grave accent + {"\u00F3", "ó"}, // � - lowercase o, acute accent + {"\u00F4", "ô"}, // � - lowercase o, circumflex accent + {"\u00F5", "õ"}, // � - lowercase o, tilde + {"\u00F6", "ö"}, // � - lowercase o, umlaut + {"\u00F7", "÷"}, // division sign + {"\u00F8", "ø"}, // � - lowercase o, slash + {"\u00F9", "ù"}, // � - lowercase u, grave accent + {"\u00FA", "ú"}, // � - lowercase u, acute accent + {"\u00FB", "û"}, // � - lowercase u, circumflex accent + {"\u00FC", "ü"}, // � - lowercase u, umlaut + {"\u00FD", "ý"}, // � - lowercase y, acute accent + {"\u00FE", "þ"}, // � - lowercase thorn, Icelandic + {"\u00FF", "ÿ"}, // � - lowercase y, umlaut + }; + + /** + * Reverse of {@link #ISO8859_1_ESCAPE()} for unescaping purposes. + * @return the mapping table + */ + public static String[][] ISO8859_1_UNESCAPE() { return ISO8859_1_UNESCAPE.clone(); } + private static final String[][] ISO8859_1_UNESCAPE = invert(ISO8859_1_ESCAPE); + + /** + * Mapping to escape additional character entity + * references. Note that this must be used with {@link #ISO8859_1_ESCAPE()} to get the full list of + * HTML 4.0 character entities. + * @return the mapping table + */ + public static String[][] HTML40_EXTENDED_ESCAPE() { return HTML40_EXTENDED_ESCAPE.clone(); } + private static final String[][] HTML40_EXTENDED_ESCAPE = { + // + {"\u0192", "ƒ"}, // latin small f with hook = function= florin, U+0192 ISOtech --> + // + {"\u0391", "Α"}, // greek capital letter alpha, U+0391 --> + {"\u0392", "Β"}, // greek capital letter beta, U+0392 --> + {"\u0393", "Γ"}, // greek capital letter gamma,U+0393 ISOgrk3 --> + {"\u0394", "Δ"}, // greek capital letter delta,U+0394 ISOgrk3 --> + {"\u0395", "Ε"}, // greek capital letter epsilon, U+0395 --> + {"\u0396", "Ζ"}, // greek capital letter zeta, U+0396 --> + {"\u0397", "Η"}, // greek capital letter eta, U+0397 --> + {"\u0398", "Θ"}, // greek capital letter theta,U+0398 ISOgrk3 --> + {"\u0399", "Ι"}, // greek capital letter iota, U+0399 --> + {"\u039A", "Κ"}, // greek capital letter kappa, U+039A --> + {"\u039B", "Λ"}, // greek capital letter lambda,U+039B ISOgrk3 --> + {"\u039C", "Μ"}, // greek capital letter mu, U+039C --> + {"\u039D", "Ν"}, // greek capital letter nu, U+039D --> + {"\u039E", "Ξ"}, // greek capital letter xi, U+039E ISOgrk3 --> + {"\u039F", "Ο"}, // greek capital letter omicron, U+039F --> + {"\u03A0", "Π"}, // greek capital letter pi, U+03A0 ISOgrk3 --> + {"\u03A1", "Ρ"}, // greek capital letter rho, U+03A1 --> + // + {"\u03A3", "Σ"}, // greek capital letter sigma,U+03A3 ISOgrk3 --> + {"\u03A4", "Τ"}, // greek capital letter tau, U+03A4 --> + {"\u03A5", "Υ"}, // greek capital letter upsilon,U+03A5 ISOgrk3 --> + {"\u03A6", "Φ"}, // greek capital letter phi,U+03A6 ISOgrk3 --> + {"\u03A7", "Χ"}, // greek capital letter chi, U+03A7 --> + {"\u03A8", "Ψ"}, // greek capital letter psi,U+03A8 ISOgrk3 --> + {"\u03A9", "Ω"}, // greek capital letter omega,U+03A9 ISOgrk3 --> + {"\u03B1", "α"}, // greek small letter alpha,U+03B1 ISOgrk3 --> + {"\u03B2", "β"}, // greek small letter beta, U+03B2 ISOgrk3 --> + {"\u03B3", "γ"}, // greek small letter gamma,U+03B3 ISOgrk3 --> + {"\u03B4", "δ"}, // greek small letter delta,U+03B4 ISOgrk3 --> + {"\u03B5", "ε"}, // greek small letter epsilon,U+03B5 ISOgrk3 --> + {"\u03B6", "ζ"}, // greek small letter zeta, U+03B6 ISOgrk3 --> + {"\u03B7", "η"}, // greek small letter eta, U+03B7 ISOgrk3 --> + {"\u03B8", "θ"}, // greek small letter theta,U+03B8 ISOgrk3 --> + {"\u03B9", "ι"}, // greek small letter iota, U+03B9 ISOgrk3 --> + {"\u03BA", "κ"}, // greek small letter kappa,U+03BA ISOgrk3 --> + {"\u03BB", "λ"}, // greek small letter lambda,U+03BB ISOgrk3 --> + {"\u03BC", "μ"}, // greek small letter mu, U+03BC ISOgrk3 --> + {"\u03BD", "ν"}, // greek small letter nu, U+03BD ISOgrk3 --> + {"\u03BE", "ξ"}, // greek small letter xi, U+03BE ISOgrk3 --> + {"\u03BF", "ο"}, // greek small letter omicron, U+03BF NEW --> + {"\u03C0", "π"}, // greek small letter pi, U+03C0 ISOgrk3 --> + {"\u03C1", "ρ"}, // greek small letter rho, U+03C1 ISOgrk3 --> + {"\u03C2", "ς"}, // greek small letter final sigma,U+03C2 ISOgrk3 --> + {"\u03C3", "σ"}, // greek small letter sigma,U+03C3 ISOgrk3 --> + {"\u03C4", "τ"}, // greek small letter tau, U+03C4 ISOgrk3 --> + {"\u03C5", "υ"}, // greek small letter upsilon,U+03C5 ISOgrk3 --> + {"\u03C6", "φ"}, // greek small letter phi, U+03C6 ISOgrk3 --> + {"\u03C7", "χ"}, // greek small letter chi, U+03C7 ISOgrk3 --> + {"\u03C8", "ψ"}, // greek small letter psi, U+03C8 ISOgrk3 --> + {"\u03C9", "ω"}, // greek small letter omega,U+03C9 ISOgrk3 --> + {"\u03D1", "ϑ"}, // greek small letter theta symbol,U+03D1 NEW --> + {"\u03D2", "ϒ"}, // greek upsilon with hook symbol,U+03D2 NEW --> + {"\u03D6", "ϖ"}, // greek pi symbol, U+03D6 ISOgrk3 --> + // + {"\u2022", "•"}, // bullet = black small circle,U+2022 ISOpub --> + // + {"\u2026", "…"}, // horizontal ellipsis = three dot leader,U+2026 ISOpub --> + {"\u2032", "′"}, // prime = minutes = feet, U+2032 ISOtech --> + {"\u2033", "″"}, // double prime = seconds = inches,U+2033 ISOtech --> + {"\u203E", "‾"}, // overline = spacing overscore,U+203E NEW --> + {"\u2044", "⁄"}, // fraction slash, U+2044 NEW --> + // + {"\u2118", "℘"}, // script capital P = power set= Weierstrass p, U+2118 ISOamso --> + {"\u2111", "ℑ"}, // blackletter capital I = imaginary part,U+2111 ISOamso --> + {"\u211C", "ℜ"}, // blackletter capital R = real part symbol,U+211C ISOamso --> + {"\u2122", "™"}, // trade mark sign, U+2122 ISOnum --> + {"\u2135", "ℵ"}, // alef symbol = first transfinite cardinal,U+2135 NEW --> + // + // + {"\u2190", "←"}, // leftwards arrow, U+2190 ISOnum --> + {"\u2191", "↑"}, // upwards arrow, U+2191 ISOnum--> + {"\u2192", "→"}, // rightwards arrow, U+2192 ISOnum --> + {"\u2193", "↓"}, // downwards arrow, U+2193 ISOnum --> + {"\u2194", "↔"}, // left right arrow, U+2194 ISOamsa --> + {"\u21B5", "↵"}, // downwards arrow with corner leftwards= carriage return, U+21B5 NEW --> + {"\u21D0", "⇐"}, // leftwards double arrow, U+21D0 ISOtech --> + // + {"\u21D1", "⇑"}, // upwards double arrow, U+21D1 ISOamsa --> + {"\u21D2", "⇒"}, // rightwards double arrow,U+21D2 ISOtech --> + // + {"\u21D3", "⇓"}, // downwards double arrow, U+21D3 ISOamsa --> + {"\u21D4", "⇔"}, // left right double arrow,U+21D4 ISOamsa --> + // + {"\u2200", "∀"}, // for all, U+2200 ISOtech --> + {"\u2202", "∂"}, // partial differential, U+2202 ISOtech --> + {"\u2203", "∃"}, // there exists, U+2203 ISOtech --> + {"\u2205", "∅"}, // empty set = null set = diameter,U+2205 ISOamso --> + {"\u2207", "∇"}, // nabla = backward difference,U+2207 ISOtech --> + {"\u2208", "∈"}, // element of, U+2208 ISOtech --> + {"\u2209", "∉"}, // not an element of, U+2209 ISOtech --> + {"\u220B", "∋"}, // contains as member, U+220B ISOtech --> + // + {"\u220F", "∏"}, // n-ary product = product sign,U+220F ISOamsb --> + // + {"\u2211", "∑"}, // n-ary summation, U+2211 ISOamsb --> + // + {"\u2212", "−"}, // minus sign, U+2212 ISOtech --> + {"\u2217", "∗"}, // asterisk operator, U+2217 ISOtech --> + {"\u221A", "√"}, // square root = radical sign,U+221A ISOtech --> + {"\u221D", "∝"}, // proportional to, U+221D ISOtech --> + {"\u221E", "∞"}, // infinity, U+221E ISOtech --> + {"\u2220", "∠"}, // angle, U+2220 ISOamso --> + {"\u2227", "∧"}, // logical and = wedge, U+2227 ISOtech --> + {"\u2228", "∨"}, // logical or = vee, U+2228 ISOtech --> + {"\u2229", "∩"}, // intersection = cap, U+2229 ISOtech --> + {"\u222A", "∪"}, // union = cup, U+222A ISOtech --> + {"\u222B", "∫"}, // integral, U+222B ISOtech --> + {"\u2234", "∴"}, // therefore, U+2234 ISOtech --> + {"\u223C", "∼"}, // tilde operator = varies with = similar to,U+223C ISOtech --> + // + {"\u2245", "≅"}, // approximately equal to, U+2245 ISOtech --> + {"\u2248", "≈"}, // almost equal to = asymptotic to,U+2248 ISOamsr --> + {"\u2260", "≠"}, // not equal to, U+2260 ISOtech --> + {"\u2261", "≡"}, // identical to, U+2261 ISOtech --> + {"\u2264", "≤"}, // less-than or equal to, U+2264 ISOtech --> + {"\u2265", "≥"}, // greater-than or equal to,U+2265 ISOtech --> + {"\u2282", "⊂"}, // subset of, U+2282 ISOtech --> + {"\u2283", "⊃"}, // superset of, U+2283 ISOtech --> + // + {"\u2286", "⊆"}, // subset of or equal to, U+2286 ISOtech --> + {"\u2287", "⊇"}, // superset of or equal to,U+2287 ISOtech --> + {"\u2295", "⊕"}, // circled plus = direct sum,U+2295 ISOamsb --> + {"\u2297", "⊗"}, // circled times = vector product,U+2297 ISOamsb --> + {"\u22A5", "⊥"}, // up tack = orthogonal to = perpendicular,U+22A5 ISOtech --> + {"\u22C5", "⋅"}, // dot operator, U+22C5 ISOamsb --> + // + // + {"\u2308", "⌈"}, // left ceiling = apl upstile,U+2308 ISOamsc --> + {"\u2309", "⌉"}, // right ceiling, U+2309 ISOamsc --> + {"\u230A", "⌊"}, // left floor = apl downstile,U+230A ISOamsc --> + {"\u230B", "⌋"}, // right floor, U+230B ISOamsc --> + {"\u2329", "⟨"}, // left-pointing angle bracket = bra,U+2329 ISOtech --> + // + {"\u232A", "⟩"}, // right-pointing angle bracket = ket,U+232A ISOtech --> + // + // + {"\u25CA", "◊"}, // lozenge, U+25CA ISOpub --> + // + {"\u2660", "♠"}, // black spade suit, U+2660 ISOpub --> + // + {"\u2663", "♣"}, // black club suit = shamrock,U+2663 ISOpub --> + {"\u2665", "♥"}, // black heart suit = valentine,U+2665 ISOpub --> + {"\u2666", "♦"}, // black diamond suit, U+2666 ISOpub --> + + // + {"\u0152", "Œ"}, // -- latin capital ligature OE,U+0152 ISOlat2 --> + {"\u0153", "œ"}, // -- latin small ligature oe, U+0153 ISOlat2 --> + // + {"\u0160", "Š"}, // -- latin capital letter S with caron,U+0160 ISOlat2 --> + {"\u0161", "š"}, // -- latin small letter s with caron,U+0161 ISOlat2 --> + {"\u0178", "Ÿ"}, // -- latin capital letter Y with diaeresis,U+0178 ISOlat2 --> + // + {"\u02C6", "ˆ"}, // -- modifier letter circumflex accent,U+02C6 ISOpub --> + {"\u02DC", "˜"}, // small tilde, U+02DC ISOdia --> + // + {"\u2002", " "}, // en space, U+2002 ISOpub --> + {"\u2003", " "}, // em space, U+2003 ISOpub --> + {"\u2009", " "}, // thin space, U+2009 ISOpub --> + {"\u200C", "‌"}, // zero width non-joiner,U+200C NEW RFC 2070 --> + {"\u200D", "‍"}, // zero width joiner, U+200D NEW RFC 2070 --> + {"\u200E", "‎"}, // left-to-right mark, U+200E NEW RFC 2070 --> + {"\u200F", "‏"}, // right-to-left mark, U+200F NEW RFC 2070 --> + {"\u2013", "–"}, // en dash, U+2013 ISOpub --> + {"\u2014", "—"}, // em dash, U+2014 ISOpub --> + {"\u2018", "‘"}, // left single quotation mark,U+2018 ISOnum --> + {"\u2019", "’"}, // right single quotation mark,U+2019 ISOnum --> + {"\u201A", "‚"}, // single low-9 quotation mark, U+201A NEW --> + {"\u201C", "“"}, // left double quotation mark,U+201C ISOnum --> + {"\u201D", "”"}, // right double quotation mark,U+201D ISOnum --> + {"\u201E", "„"}, // double low-9 quotation mark, U+201E NEW --> + {"\u2020", "†"}, // dagger, U+2020 ISOpub --> + {"\u2021", "‡"}, // double dagger, U+2021 ISOpub --> + {"\u2030", "‰"}, // per mille sign, U+2030 ISOtech --> + {"\u2039", "‹"}, // single left-pointing angle quotation mark,U+2039 ISO proposed --> + // + {"\u203A", "›"}, // single right-pointing angle quotation mark,U+203A ISO proposed --> + // + {"\u20AC", "€"}, // -- euro sign, U+20AC NEW --> + }; + + /** + * Reverse of {@link #HTML40_EXTENDED_ESCAPE()} for unescaping purposes. + * @return the mapping table + */ + public static String[][] HTML40_EXTENDED_UNESCAPE() { return HTML40_EXTENDED_UNESCAPE.clone(); } + private static final String[][] HTML40_EXTENDED_UNESCAPE = invert(HTML40_EXTENDED_ESCAPE); + + /** + * Mapping to escape the basic XML and HTML character entities. + * + * Namely: {@code " & < >} + * @return the mapping table + */ + public static String[][] BASIC_ESCAPE() { return BASIC_ESCAPE.clone(); } + private static final String[][] BASIC_ESCAPE = { + {"\"", """}, // " - double-quote + {"&", "&"}, // & - ampersand + {"<", "<"}, // < - less-than + {">", ">"}, // > - greater-than + }; + + /** + * Reverse of {@link #BASIC_ESCAPE()} for unescaping purposes. + * @return the mapping table + */ + public static String[][] BASIC_UNESCAPE() { return BASIC_UNESCAPE.clone(); } + private static final String[][] BASIC_UNESCAPE = invert(BASIC_ESCAPE); + + /** + * Mapping to escape the apostrophe character to its XML character entity. + * @return the mapping table + */ + public static String[][] APOS_ESCAPE() { return APOS_ESCAPE.clone(); } + private static final String[][] APOS_ESCAPE = { + {"'", "'"}, // XML apostrophe + }; + + /** + * Reverse of {@link #APOS_ESCAPE()} for unescaping purposes. + * @return the mapping table + */ + public static String[][] APOS_UNESCAPE() { return APOS_UNESCAPE.clone(); } + private static final String[][] APOS_UNESCAPE = invert(APOS_ESCAPE); + + /** + * Mapping to escape the Java control characters. + * + * Namely: {@code \b \n \t \f \r} + * @return the mapping table + */ + public static String[][] JAVA_CTRL_CHARS_ESCAPE() { return JAVA_CTRL_CHARS_ESCAPE.clone(); } + private static final String[][] JAVA_CTRL_CHARS_ESCAPE = { + {"\b", "\\b"}, + {"\n", "\\n"}, + {"\t", "\\t"}, + {"\f", "\\f"}, + {"\r", "\\r"} + }; + + /** + * Reverse of {@link #JAVA_CTRL_CHARS_ESCAPE()} for unescaping purposes. + * @return the mapping table + */ + public static String[][] JAVA_CTRL_CHARS_UNESCAPE() { return JAVA_CTRL_CHARS_UNESCAPE.clone(); } + private static final String[][] JAVA_CTRL_CHARS_UNESCAPE = invert(JAVA_CTRL_CHARS_ESCAPE); + + /** + * Used to invert an escape array into an unescape array + * @param array String[][] to be inverted + * @return String[][] inverted array + */ + public static String[][] invert(final String[][] array) { + final String[][] newarray = new String[array.length][2]; + for(int i = 0; i + * Constructs a JavaUnicodeEscaper above the specified value (exclusive). + *

              + * + * @param codepoint + * above which to escape + * @return the newly created {@code UnicodeEscaper} instance + */ + public static JavaUnicodeEscaper above(final int codepoint) { + return outsideOf(0, codepoint); + } + + /** + *

              + * Constructs a JavaUnicodeEscaper below the specified value (exclusive). + *

              + * + * @param codepoint + * below which to escape + * @return the newly created {@code UnicodeEscaper} instance + */ + public static JavaUnicodeEscaper below(final int codepoint) { + return outsideOf(codepoint, Integer.MAX_VALUE); + } + + /** + *

              + * Constructs a JavaUnicodeEscaper between the specified values (inclusive). + *

              + * + * @param codepointLow + * above which to escape + * @param codepointHigh + * below which to escape + * @return the newly created {@code UnicodeEscaper} instance + */ + public static JavaUnicodeEscaper between(final int codepointLow, final int codepointHigh) { + return new JavaUnicodeEscaper(codepointLow, codepointHigh, true); + } + + /** + *

              + * Constructs a JavaUnicodeEscaper outside of the specified values (exclusive). + *

              + * + * @param codepointLow + * below which to escape + * @param codepointHigh + * above which to escape + * @return the newly created {@code UnicodeEscaper} instance + */ + public static JavaUnicodeEscaper outsideOf(final int codepointLow, final int codepointHigh) { + return new JavaUnicodeEscaper(codepointLow, codepointHigh, false); + } + + /** + *

              + * Constructs a JavaUnicodeEscaper for the specified range. This is the underlying method for the + * other constructors/builders. The below and above boundaries are inclusive when + * between is true and exclusive when it is false. + *

              + * + * @param below + * int value representing the lowest codepoint boundary + * @param above + * int value representing the highest codepoint boundary + * @param between + * whether to escape between the boundaries or outside them + */ + public JavaUnicodeEscaper(final int below, final int above, final boolean between) { + super(below, above, between); + } + + /** + * Converts the given codepoint to a hex string of the form {@code "\\uXXXX\\uXXXX"} + * + * @param codepoint + * a Unicode code point + * @return the hex string for the given codepoint + */ + @Override + protected String toUtf16Escape(final int codepoint) { + final char[] surrogatePair = Character.toChars(codepoint); + return "\\u" + hex(surrogatePair[0]) + "\\u" + hex(surrogatePair[1]); + } + +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/LookupTranslator.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/LookupTranslator.java new file mode 100644 index 000000000..927589581 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/LookupTranslator.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text.translate; + +import java.io.IOException; +import java.io.Writer; +import java.util.HashMap; +import java.util.HashSet; + +/** + * Translates a value using a lookup table. + * + * @since 3.0 + * @version $Id: LookupTranslator.java 1669520 2015-03-27 08:03:41Z britter $ + */ +public class LookupTranslator extends CharSequenceTranslator { + + private final HashMap lookupMap; + private final HashSet prefixSet; + private final int shortest; + private final int longest; + + /** + * Define the lookup table to be used in translation + * + * Note that, as of Lang 3.1, the key to the lookup table is converted to a + * java.lang.String. This is because we need the key to support hashCode and + * equals(Object), allowing it to be the key for a HashMap. See LANG-882. + * + * @param lookup CharSequence[][] table of size [*][2] + */ + public LookupTranslator(final CharSequence[]... lookup) { + lookupMap = new HashMap(); + prefixSet = new HashSet(); + int _shortest = Integer.MAX_VALUE; + int _longest = 0; + if (lookup != null) { + for (final CharSequence[] seq : lookup) { + this.lookupMap.put(seq[0].toString(), seq[1].toString()); + this.prefixSet.add(seq[0].charAt(0)); + final int sz = seq[0].length(); + if (sz < _shortest) { + _shortest = sz; + } + if (sz > _longest) { + _longest = sz; + } + } + } + shortest = _shortest; + longest = _longest; + } + + /** + * {@inheritDoc} + */ + @Override + public int translate(final CharSequence input, final int index, final Writer out) throws IOException { + // check if translation exists for the input at position index + if (prefixSet.contains(input.charAt(index))) { + int max = longest; + if (index + longest > input.length()) { + max = input.length() - index; + } + // implement greedy algorithm by trying maximum match first + for (int i = max; i >= shortest; i--) { + final CharSequence subSeq = input.subSequence(index, index + i); + final String result = lookupMap.get(subSeq.toString()); + + if (result != null) { + out.write(result); + return i; + } + } + } + return 0; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java new file mode 100644 index 000000000..562ae89a5 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text.translate; + +import java.io.IOException; +import java.io.Writer; + +/** + * Translates codepoints to their XML numeric entity escaped value. + * + * @since 3.0 + * @version $Id: NumericEntityEscaper.java 1436768 2013-01-22 07:07:42Z ggregory $ + */ +public class NumericEntityEscaper extends CodePointTranslator { + + private final int below; + private final int above; + private final boolean between; + + /** + *

              Constructs a NumericEntityEscaper for the specified range. This is + * the underlying method for the other constructors/builders. The below + * and above boundaries are inclusive when between is + * true and exclusive when it is false.

              + * + * @param below int value representing the lowest codepoint boundary + * @param above int value representing the highest codepoint boundary + * @param between whether to escape between the boundaries or outside them + */ + private NumericEntityEscaper(final int below, final int above, final boolean between) { + this.below = below; + this.above = above; + this.between = between; + } + + /** + *

              Constructs a NumericEntityEscaper for all characters.

              + */ + public NumericEntityEscaper() { + this(0, Integer.MAX_VALUE, true); + } + + /** + *

              Constructs a NumericEntityEscaper below the specified value (exclusive).

              + * + * @param codepoint below which to escape + * @return the newly created {@code NumericEntityEscaper} instance + */ + public static NumericEntityEscaper below(final int codepoint) { + return outsideOf(codepoint, Integer.MAX_VALUE); + } + + /** + *

              Constructs a NumericEntityEscaper above the specified value (exclusive).

              + * + * @param codepoint above which to escape + * @return the newly created {@code NumericEntityEscaper} instance + */ + public static NumericEntityEscaper above(final int codepoint) { + return outsideOf(0, codepoint); + } + + /** + *

              Constructs a NumericEntityEscaper between the specified values (inclusive).

              + * + * @param codepointLow above which to escape + * @param codepointHigh below which to escape + * @return the newly created {@code NumericEntityEscaper} instance + */ + public static NumericEntityEscaper between(final int codepointLow, final int codepointHigh) { + return new NumericEntityEscaper(codepointLow, codepointHigh, true); + } + + /** + *

              Constructs a NumericEntityEscaper outside of the specified values (exclusive).

              + * + * @param codepointLow below which to escape + * @param codepointHigh above which to escape + * @return the newly created {@code NumericEntityEscaper} instance + */ + public static NumericEntityEscaper outsideOf(final int codepointLow, final int codepointHigh) { + return new NumericEntityEscaper(codepointLow, codepointHigh, false); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean translate(final int codepoint, final Writer out) throws IOException { + if(between) { + if (codepoint < below || codepoint > above) { + return false; + } + } else { + if (codepoint >= below && codepoint <= above) { + return false; + } + } + + out.write("&#"); + out.write(Integer.toString(codepoint, 10)); + out.write(';'); + return true; + } +} diff --git a/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java new file mode 100644 index 000000000..1bac3e592 --- /dev/null +++ b/fine-commons-lang3/src/com/fr/third/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.fr.third.org.apache.commons.lang3.text.translate; + +import java.io.IOException; +import java.io.Writer; +import java.util.Arrays; +import java.util.EnumSet; + +/** + * Translate XML numeric entities of the form &#[xX]?\d+;? to + * the specific codepoint. + * + * Note that the semi-colon is optional. + * + * @since 3.0 + * @version $Id: NumericEntityUnescaper.java 1583482 2014-03-31 22:54:57Z niallp $ + */ +public class NumericEntityUnescaper extends CharSequenceTranslator { + + public static enum OPTION { semiColonRequired, semiColonOptional, errorIfNoSemiColon } + + // TODO?: Create an OptionsSet class to hide some of the conditional logic below + private final EnumSet