diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 50375a8b65..ccb14ee392 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -27,42 +27,23 @@ resources: jobs: - template: build-templates/gradle-common.yml@templates parameters: - jdkVersion: '1.8' - jobName: 'LinuxJava8' - gradleTasks: 'check -x dependencyCheckAggregate' - -- template: build-templates/gradle-common.yml@templates - parameters: - jdkVersion: '1.8' - options: '-PtestVM=java11Home' - jobName: 'LinuxJava11' - gradleTasks: 'check -x dependencyCheckAggregate' - -- template: build-templates/gradle-common.yml@templates - parameters: - jdkVersion: '1.8' + jdkVersion: '17' options: '-PtestVM=java17Home' jobName: 'LinuxJava17' gradleTasks: 'check -x dependencyCheckAggregate' - template: build-templates/gradle-common.yml@templates parameters: - jdkVersion: '1.8' + jdkVersion: '17' options: '-PtestVM=java21Home' jobName: 'LinuxJava21' gradleTasks: 'check -x dependencyCheckAggregate' -- template: build-templates/gradle-common.yml@templates - parameters: - vmImage: 'windows-latest' - jdkVersion: '1.8' - jobName: 'WindowsJava8' - gradleTasks: 'check -x dependencyCheckAggregate' - template: build-templates/gradle-common.yml@templates parameters: vmImage: 'windows-latest' - jdkVersion: '1.8' + jdkVersion: '17' options: '-PtestVM=java21Home' jobName: 'WindowsJava21' gradleTasks: 'check -x dependencyCheckAggregate' diff --git a/build-logic/src/main/java/org/ehcache/build/conventions/JacocoConvention.java b/build-logic/src/main/java/org/ehcache/build/conventions/JacocoConvention.java index 1be256d031..7e1e4d2f82 100644 --- a/build-logic/src/main/java/org/ehcache/build/conventions/JacocoConvention.java +++ b/build-logic/src/main/java/org/ehcache/build/conventions/JacocoConvention.java @@ -27,6 +27,7 @@ public void apply(Project project) { project.getTasks().withType(Test.class).configureEach(test -> { test.getExtensions().configure(JacocoTaskExtension.class, jacoco -> { jacoco.getExcludes().add("org.terracotta.tripwire.*"); + jacoco.getExcludes().add("org.terracotta.management.*"); }); }); } diff --git a/build-logic/src/main/java/org/ehcache/build/conventions/JavaBaseConvention.java b/build-logic/src/main/java/org/ehcache/build/conventions/JavaBaseConvention.java index 64dd065fa5..7a2787a600 100644 --- a/build-logic/src/main/java/org/ehcache/build/conventions/JavaBaseConvention.java +++ b/build-logic/src/main/java/org/ehcache/build/conventions/JavaBaseConvention.java @@ -40,8 +40,8 @@ public void apply(Project project) { project.getExtensions().getExtraProperties().set("testJava", testJava); project.getExtensions().configure(JavaPluginExtension.class, java -> { - java.setSourceCompatibility(JavaVersion.VERSION_1_8); - java.setTargetCompatibility(JavaVersion.VERSION_1_8); + java.setSourceCompatibility(JavaVersion.VERSION_17); + java.setTargetCompatibility(JavaVersion.VERSION_17); }); project.getTasks().withType(Jar.class).configureEach(jar -> { diff --git a/build-logic/src/main/java/org/ehcache/build/plugins/VoltronPlugin.java b/build-logic/src/main/java/org/ehcache/build/plugins/VoltronPlugin.java index db5299198f..6959712041 100644 --- a/build-logic/src/main/java/org/ehcache/build/plugins/VoltronPlugin.java +++ b/build-logic/src/main/java/org/ehcache/build/plugins/VoltronPlugin.java @@ -14,6 +14,8 @@ import java.io.File; import java.util.jar.Attributes; import java.util.stream.Collectors; +import java.util.Map; +import java.util.HashMap; import static java.util.Collections.singletonMap; @@ -30,14 +32,11 @@ public void apply(Project project) { config.setDescription("Dependencies provided by Voltron from server/lib"); config.setCanBeConsumed(true); config.setCanBeResolved(true); + config.exclude(Map.of("group", "ch.qos.logback")); DependencyHandler dependencyHandler = project.getDependencies(); String terracottaApisVersion = project.property("terracottaApisVersion").toString(); - String slf4jVersion = project.property("slf4jVersion").toString(); - config.getDependencies().add(dependencyHandler.create("org.terracotta:entity-server-api:" + terracottaApisVersion)); - config.getDependencies().add(dependencyHandler.create("org.terracotta:standard-cluster-services:" + terracottaApisVersion)); - config.getDependencies().add(dependencyHandler.create("org.terracotta:packaging-support:" + terracottaApisVersion)); - config.getDependencies().add(dependencyHandler.create("org.slf4j:slf4j-api:" + slf4jVersion)); + config.getDependencies().add(dependencyHandler.create("org.terracotta:server-api:" + terracottaApisVersion)); }); NamedDomainObjectProvider service = project.getConfigurations().register(SERVICE_CONFIGURATION_NAME, config -> { @@ -46,7 +45,12 @@ public void apply(Project project) { config.setCanBeConsumed(true); }); - project.getConfigurations().named(JavaPlugin.API_CONFIGURATION_NAME, config -> { + project.getConfigurations().named(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, config -> { + config.extendsFrom(voltron.get()); + config.extendsFrom(service.get()); + }); + + project.getConfigurations().named(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME, config -> { config.extendsFrom(voltron.get()); config.extendsFrom(service.get()); }); diff --git a/clustered/ehcache-client/build.gradle b/clustered/ehcache-client/build.gradle index 77dbac4ca6..ea3f5c2854 100644 --- a/clustered/ehcache-client/build.gradle +++ b/clustered/ehcache-client/build.gradle @@ -31,16 +31,19 @@ dependencies { compileOnlyApi project(':ehcache-xml:ehcache-xml-spi') - implementation project(':clustered:ehcache-common') - implementation "org.terracotta:entity-client-api:$terracottaApisVersion" - implementation "org.terracotta:runnel:$terracottaPlatformVersion" - implementation "org.terracotta:lease-api:$terracottaPlatformVersion" - implementation "org.terracotta.dynamic-config.entities:dynamic-config-topology-entity-client:$terracottaPlatformVersion" - implementation "org.terracotta:connection-api:$terracottaApisVersion" + api project(':clustered:ehcache-common') + implementation "org.terracotta:client-api:$terracottaApisVersion" + implementation "org.terracotta:terracotta-runnel:$terracottaPlatformVersion" + implementation "org.terracotta:terracotta-lease-client:$terracottaPlatformVersion" + implementation "org.terracotta:terracotta-dynamic-config-entities-topology-client:$terracottaPlatformVersion" implementation "org.terracotta:terracotta-utilities-tools:$terracottaUtilitiesVersion" compileOnly 'org.osgi:org.osgi.service.component.annotations:1.3.0' + testImplementation "org.terracotta:terracotta-resources-offheap:$terracottaPlatformVersion" + testImplementation "org.terracotta:terracotta-management-server-api:$terracottaPlatformVersion" + testImplementation project(':clustered:server:ehcache-service-api') + testImplementation(project(':ehcache-transactions')) { capabilities { requireCapability("org.ehcache:ehcache-transactions-modules") @@ -50,15 +53,18 @@ dependencies { exclude group: 'org.terracotta.internal', module: 'tc-config-parser' } testImplementation project(':clustered:server:ehcache-service') - testImplementation "org.terracotta:client-message-tracker:$terracottaPlatformVersion" + testImplementation "org.terracotta:terracotta-client-message-tracker:$terracottaPlatformVersion" testImplementation project(':clustered:test-utils') - testImplementation "org.terracotta:entity-test-lib:$terracottaPassthroughTestingVersion" - testImplementation "org.terracotta:passthrough-server:$terracottaPassthroughTestingVersion" - testImplementation "org.terracotta.internal:common-spi:$terracottaCoreVersion" - testImplementation "org.terracotta:passthrough-leased-connection-api:$terracottaPlatformVersion" + testImplementation ("org.terracotta:passthrough-server:$terracottaRuntimeVersion") { + exclude group: "ch.qos.logback" + } + testImplementation "org.terracotta:terracotta-lease-testing-passthrough:$terracottaPlatformVersion" testImplementation (group: 'org.codehaus.btm', name: 'btm', version: '2.1.4') { exclude group:'org.slf4j', module:'slf4j-api' } testImplementation testFixtures(project(':ehcache-xml')) testImplementation ("org.terracotta:statistics:$parent.statisticVersion") + testImplementation ("org.terracotta:terracotta-structures:$terracottaPlatformVersion") { + exclude group: 'org.slf4j', module: 'slf4j-api' + } } diff --git a/clustered/ehcache-client/src/main/java/org/ehcache/clustered/client/internal/service/DefaultClusteringService.java b/clustered/ehcache-client/src/main/java/org/ehcache/clustered/client/internal/service/DefaultClusteringService.java index eaf162c771..bd18840dd5 100644 --- a/clustered/ehcache-client/src/main/java/org/ehcache/clustered/client/internal/service/DefaultClusteringService.java +++ b/clustered/ehcache-client/src/main/java/org/ehcache/clustered/client/internal/service/DefaultClusteringService.java @@ -377,6 +377,7 @@ public ConnectionState getConnectionState() { return connectionState; } + @SuppressWarnings("removal") private static ExecutorService createAsyncWorker() { SecurityManager s = System.getSecurityManager(); ThreadGroup initialGroup = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); diff --git a/clustered/ehcache-client/src/main/java/org/ehcache/clustered/client/internal/store/CommonServerStoreProxy.java b/clustered/ehcache-client/src/main/java/org/ehcache/clustered/client/internal/store/CommonServerStoreProxy.java index 2d608c9028..370abc558a 100644 --- a/clustered/ehcache-client/src/main/java/org/ehcache/clustered/client/internal/store/CommonServerStoreProxy.java +++ b/clustered/ehcache-client/src/main/java/org/ehcache/clustered/client/internal/store/CommonServerStoreProxy.java @@ -117,7 +117,6 @@ void addResponseListener(Class listenerClas entity.addResponseListener(listenerClass, listener); } - @SuppressWarnings("unchecked") @Override public void close() { entity.close(); @@ -232,6 +231,8 @@ public Map.Entry next() { } @Override + @SuppressWarnings("deprecation") + // TODO: FIXME: Use the new Cleaner API protected void finalize() throws Throwable { if (!lastBatch) { entity.invokeAndWaitForReceive(new ServerStoreOpMessage.IteratorCloseMessage(iteratorId), false); diff --git a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/UnitTestConnectionService.java b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/UnitTestConnectionService.java index 885a67176a..f71a0a0419 100644 --- a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/UnitTestConnectionService.java +++ b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/UnitTestConnectionService.java @@ -23,7 +23,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.math.BigInteger; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; @@ -36,7 +35,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.stream.Collectors; +import static java.util.stream.Collectors.toList; import org.ehcache.clustered.client.internal.lock.VoltronReadWriteLockEntityClientService; import org.ehcache.clustered.client.internal.store.ClusterTierClientEntityService; @@ -45,8 +44,11 @@ import org.ehcache.clustered.server.store.ClusterTierServerEntityService; import org.ehcache.impl.internal.concurrent.ConcurrentHashMap; +import static org.mockito.Mockito.mock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.terracotta.common.struct.Measure; +import org.terracotta.common.struct.MemoryUnit; import org.terracotta.connection.Connection; import org.terracotta.connection.ConnectionException; import org.terracotta.connection.ConnectionPropertyNames; @@ -63,19 +65,12 @@ import org.terracotta.exception.EntityNotProvidedException; import org.terracotta.exception.PermanentEntityException; import org.terracotta.offheapresource.OffHeapResourcesProvider; -import org.terracotta.offheapresource.config.MemoryUnit; -import org.terracotta.offheapresource.config.OffheapResourcesType; -import org.terracotta.offheapresource.config.ResourceType; import org.terracotta.passthrough.IAsynchronousServerCrasher; import org.terracotta.passthrough.PassthroughConnection; import org.terracotta.passthrough.PassthroughServer; import org.terracotta.passthrough.PassthroughServerRegistry; import static java.util.Collections.synchronizedMap; -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toList; -import static org.mockito.Mockito.mock; - /** * A {@link ConnectionService} implementation used to simulate Voltron server connections for unit testing purposes. @@ -176,21 +171,6 @@ public static void removeStripe(String stripeName) { } stripeDescriptor.removeConnections(); } - - public static OffheapResourcesType getOffheapResourcesType(String resourceName, int size, MemoryUnit unit) { - OffheapResourcesType resources = new OffheapResourcesType(); - resources.getResource().add(getResource(resourceName, size, unit)); - return resources; - } - - private static ResourceType getResource(String resourceName, int size, MemoryUnit unit) { - final ResourceType resource = new ResourceType(); - resource.setName(resourceName); - resource.setUnit(unit); - resource.setValue(BigInteger.valueOf((long)size)); - return resource; - } - /** * Adds a {@link PassthroughServer} if, and only if, a mapping for the URI supplied does not * already exist. The server is started as it is added. @@ -288,7 +268,7 @@ public static final class PassthroughServerBuilder { private final Map serviceProviders = new IdentityHashMap<>(); - private final OffheapResourcesType resources = new OffheapResourcesType(); + private final Map> resources = new HashMap<>(); public PassthroughServerBuilder resource(String resourceName, int size, org.ehcache.config.units.MemoryUnit unit) { return this.resource(resourceName, size, convert(unit)); @@ -301,7 +281,7 @@ private MemoryUnit convert(org.ehcache.config.units.MemoryUnit unit) { convertedUnit = MemoryUnit.B; break; case KB: - convertedUnit = MemoryUnit.K_B; + convertedUnit = MemoryUnit.KB; break; case MB: convertedUnit = MemoryUnit.MB; @@ -322,7 +302,7 @@ private MemoryUnit convert(org.ehcache.config.units.MemoryUnit unit) { } private PassthroughServerBuilder resource(String resourceName, int size, MemoryUnit unit) { - this.resources.getResource().add(getResource(resourceName, size, unit)); + this.resources.put(resourceName, Measure.of(size, unit)); return this; } diff --git a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/ClusterStateRepositoryReplicationTest.java b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/ClusterStateRepositoryReplicationTest.java index 6b4ae8b178..e17053da76 100644 --- a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/ClusterStateRepositoryReplicationTest.java +++ b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/ClusterStateRepositoryReplicationTest.java @@ -39,17 +39,16 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.terracotta.offheapresource.OffHeapResourceIdentifier; import org.terracotta.offheapresource.OffHeapResourcesProvider; -import org.terracotta.offheapresource.config.MemoryUnit; import org.terracotta.passthrough.PassthroughClusterControl; import org.terracotta.passthrough.PassthroughTestHelpers; import java.io.Serializable; import java.lang.reflect.Field; import java.net.URI; - +import java.util.Collections; import static org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder.clusteredDedicated; -import static org.ehcache.clustered.client.internal.UnitTestConnectionService.getOffheapResourcesType; import static org.ehcache.config.Eviction.noAdvice; import static org.ehcache.config.builders.ExpiryPolicyBuilder.noExpiration; import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder; @@ -74,7 +73,9 @@ public void setUp() throws Exception { server.registerClientEntityService(new ClusterTierClientEntityService()); server.registerServerEntityService(new VoltronReadWriteLockServerEntityService()); server.registerClientEntityService(new VoltronReadWriteLockEntityClientService()); - server.registerExtendedConfiguration(new OffHeapResourcesProvider(getOffheapResourcesType("test", 32, MemoryUnit.MB))); + OffHeapResourcesProvider offheapResources = new OffHeapResourcesProvider(Collections.emptyMap()); + offheapResources.addOffHeapResource(OffHeapResourceIdentifier.identifier("test"), 32 * 1024 * 1024); + server.registerExtendedConfiguration(offheapResources); UnitTestConnectionService.addServerToStripe(STRIPENAME, server); } diff --git a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/ReconnectTest.java b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/ReconnectTest.java index 8ee1527a3a..385b4c15fc 100644 --- a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/ReconnectTest.java +++ b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/ReconnectTest.java @@ -78,8 +78,10 @@ public void testAfterConnectionReconnectHappensEvenAfterConnectionException() th MockConnectionService.mockConnection = Mockito.mock(Connection.class, Mockito.withSettings().defaultAnswer(invocation -> { throw new RuntimeException("Stop reconnecting"); })); - while (connectionState.getReconnectCount() == 1) { - break; + while (true) { + if (connectionState.getReconnectCount() > 0) { + break; + } } }); @@ -91,7 +93,7 @@ public void testAfterConnectionReconnectHappensEvenAfterConnectionException() th assertThat(e.getCause().getMessage(), Matchers.is("Stop reconnecting")); } - assertThat(connectionState.getReconnectCount(), Matchers.is(1)); + assertThat(connectionState.getReconnectCount(), Matchers.greaterThanOrEqualTo(1)); } diff --git a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/StateRepositoryWhitelistingTest.java b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/StateRepositoryWhitelistingTest.java index 48274dab70..07e62ab0cc 100644 --- a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/StateRepositoryWhitelistingTest.java +++ b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/service/StateRepositoryWhitelistingTest.java @@ -38,8 +38,8 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.terracotta.offheapresource.OffHeapResourceIdentifier; import org.terracotta.offheapresource.OffHeapResourcesProvider; -import org.terracotta.offheapresource.config.MemoryUnit; import org.terracotta.passthrough.PassthroughClusterControl; import org.terracotta.passthrough.PassthroughTestHelpers; @@ -47,9 +47,8 @@ import java.lang.reflect.Field; import java.net.URI; import java.util.Arrays; - +import java.util.Collections; import static org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder.clusteredDedicated; -import static org.ehcache.clustered.client.internal.UnitTestConnectionService.getOffheapResourcesType; import static org.ehcache.config.Eviction.noAdvice; import static org.ehcache.config.builders.ExpiryPolicyBuilder.noExpiration; import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder; @@ -77,7 +76,9 @@ public void setUp() throws Exception { server.registerClientEntityService(new ClusterTierClientEntityService()); server.registerServerEntityService(new VoltronReadWriteLockServerEntityService()); server.registerClientEntityService(new VoltronReadWriteLockEntityClientService()); - server.registerExtendedConfiguration(new OffHeapResourcesProvider(getOffheapResourcesType("test", 32, MemoryUnit.MB))); + OffHeapResourcesProvider offheapResources = new OffHeapResourcesProvider(Collections.emptyMap()); + offheapResources.addOffHeapResource(OffHeapResourceIdentifier.identifier("test"), 32 * 1024 * 1024); + server.registerExtendedConfiguration(offheapResources); UnitTestConnectionService.addServerToStripe(STRIPENAME, server); } @@ -166,9 +167,9 @@ public void testWhitelistingForPrimitiveClass() throws Exception { StateHolder testMap = stateRepository.getPersistentStateHolder("testMap", Integer.class, Integer.class, Arrays.asList(Child.class)::contains, null); - testMap.putIfAbsent(new Integer(10), new Integer(20)); + testMap.putIfAbsent(Integer.valueOf(10), Integer.valueOf(20)); - assertThat(testMap.get(new Integer(10)), is(new Integer(20))); + assertThat(testMap.get(Integer.valueOf(10)), is(Integer.valueOf(20))); assertThat(testMap.entrySet(), hasSize(1)); } diff --git a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/store/ClusteredStoreTest.java b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/store/ClusteredStoreTest.java index 750e53c0b5..be6954cc87 100644 --- a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/store/ClusteredStoreTest.java +++ b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/store/ClusteredStoreTest.java @@ -17,7 +17,6 @@ package org.ehcache.clustered.client.internal.store; -import com.google.common.base.Objects; import org.assertj.core.api.ThrowableAssert; import org.ehcache.Cache; import org.ehcache.clustered.client.TestTimeSource; @@ -63,6 +62,7 @@ import java.util.HashSet; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Properties; import java.util.concurrent.TimeoutException; import java.util.function.Function; @@ -228,10 +228,9 @@ public void testGetThrowsOnlySAE() throws Exception { } @Test - @SuppressWarnings("unchecked") public void testGetTimeout() throws Exception { ServerStoreProxy proxy = mock(ServerStoreProxy.class); - long longKey = HashUtils.intHashToLong(new Long(1L).hashCode()); + long longKey = HashUtils.intHashToLong(Long.valueOf(1L).hashCode()); when(proxy.get(longKey)).thenThrow(TimeoutException.class); ClusteredStore store = new ClusteredStore<>(config,null, null, proxy, null, null, new DefaultStatisticsService()); assertThat(store.get(1L), nullValue()); @@ -726,7 +725,7 @@ public void describeTo(Description description) { @Override protected boolean matchesSafely(Cache.Entry> item) { - return Objects.equal(key, item.getKey()) && Objects.equal(value, item.getValue().get()); + return Objects.equals(key, item.getKey()) && Objects.equals(value, item.getValue().get()); } }; } diff --git a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/store/lock/LockRetentionDuringFailoverTest.java b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/store/lock/LockRetentionDuringFailoverTest.java index 1996de5393..186607138b 100644 --- a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/store/lock/LockRetentionDuringFailoverTest.java +++ b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/internal/store/lock/LockRetentionDuringFailoverTest.java @@ -32,12 +32,13 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.terracotta.offheapresource.OffHeapResourceIdentifier; import org.terracotta.offheapresource.OffHeapResourcesProvider; -import org.terracotta.offheapresource.config.MemoryUnit; import org.terracotta.passthrough.PassthroughClusterControl; import org.terracotta.passthrough.PassthroughTestHelpers; import java.net.URI; +import java.util.Collections; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -46,7 +47,6 @@ import static org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder.clusteredDedicated; import static org.ehcache.clustered.client.config.builders.ClusteringServiceConfigurationBuilder.cluster; -import static org.ehcache.clustered.client.internal.UnitTestConnectionService.getOffheapResourcesType; import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -73,7 +73,9 @@ public void setUp() throws Exception { server.registerClientEntityService(new ClusterTierClientEntityService()); server.registerServerEntityService(new VoltronReadWriteLockServerEntityService()); server.registerClientEntityService(new VoltronReadWriteLockEntityClientService()); - server.registerExtendedConfiguration(new OffHeapResourcesProvider(getOffheapResourcesType("test", 32, MemoryUnit.MB))); + OffHeapResourcesProvider offheapResources = new OffHeapResourcesProvider(Collections.emptyMap()); + offheapResources.addOffHeapResource(OffHeapResourceIdentifier.identifier("test"), 32 * 1024 * 1024); + server.registerExtendedConfiguration(offheapResources); UnitTestConnectionService.addServerToStripe(STRIPENAME, server); } diff --git a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/replication/ActivePassiveClientIdTest.java b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/replication/ActivePassiveClientIdTest.java index 6522015a47..f98f7a32b7 100644 --- a/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/replication/ActivePassiveClientIdTest.java +++ b/clustered/ehcache-client/src/test/java/org/ehcache/clustered/client/replication/ActivePassiveClientIdTest.java @@ -42,12 +42,13 @@ import org.junit.Before; import org.junit.Test; import org.terracotta.client.message.tracker.OOOMessageHandler; +import org.terracotta.offheapresource.OffHeapResourceIdentifier; import org.terracotta.offheapresource.OffHeapResourcesProvider; -import org.terracotta.offheapresource.config.MemoryUnit; import org.terracotta.passthrough.PassthroughClusterControl; import org.terracotta.passthrough.PassthroughTestHelpers; import java.net.URI; +import java.util.Collections; import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -57,7 +58,6 @@ import static org.ehcache.clustered.ChainUtils.chainOf; import static org.ehcache.clustered.ChainUtils.createPayload; import static org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder.clusteredDedicated; -import static org.ehcache.clustered.client.internal.UnitTestConnectionService.getOffheapResourcesType; import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder; import static org.mockito.Mockito.mock; @@ -94,7 +94,9 @@ public void setUp() throws Exception { server.registerClientEntityService(new ClusterTierClientEntityService()); server.registerServerEntityService(new VoltronReadWriteLockServerEntityService()); server.registerClientEntityService(new VoltronReadWriteLockEntityClientService()); - server.registerExtendedConfiguration(new OffHeapResourcesProvider(getOffheapResourcesType("test", 32, MemoryUnit.MB))); + OffHeapResourcesProvider offheapResources = new OffHeapResourcesProvider(Collections.emptyMap()); + offheapResources.addOffHeapResource(OffHeapResourceIdentifier.identifier("test"), 32 * 1024 * 1024); + server.registerExtendedConfiguration(offheapResources); UnitTestConnectionService.addServerToStripe(STRIPENAME, server); } diff --git a/clustered/ehcache-clustered/build.gradle b/clustered/ehcache-clustered/build.gradle index c1c77ab825..3750523840 100644 --- a/clustered/ehcache-clustered/build.gradle +++ b/clustered/ehcache-clustered/build.gradle @@ -57,7 +57,7 @@ configurations { dependencies { contents project(':clustered:ehcache-client') - contents "org.terracotta.internal:client-runtime:$terracottaCoreVersion" + contents "org.terracotta.internal:client-runtime:$terracottaRuntimeVersion" implementation "org.slf4j:slf4j-api:$parent.slf4jVersion" implementation project(':ehcache') @@ -81,17 +81,18 @@ def kitProvides = { Configuration c -> configurations { kit + serverRuntime serverApis(kitProvides) serverLibs(kitProvides) } dependencies { + serverRuntime "org.terracotta.internal:server-runtime:$terracottaRuntimeVersion" serverApis project(':clustered:server:ehcache-service-api') serverLibs project(':clustered:server:ehcache-entity') serverLibs project(':clustered:server:ehcache-service') - kit "org.terracotta.internal:terracotta-kit:$terracottaCoreVersion@tar.gz" - kit "org.terracotta:platform-layout:$terracottaPlatformVersion@tar.gz" + kit "org.terracotta:terracotta-platform-layout:$terracottaPlatformVersion@tar.gz" } task copyDocs(type: Sync) { @@ -134,6 +135,18 @@ distributions { } includeEmptyDirs = false } + into ("server/lib") { + from(configurations.serverRuntime.elements.map { + files -> files.collect { + if (it.file.name.endsWith('.zip')) { + zipTree(it.file) + } else { + it.file + } + } + }) + + } into ("server/plugins/api") { from configurations.serverApis } diff --git a/clustered/ehcache-common-api/build.gradle b/clustered/ehcache-common-api/build.gradle index 2a98d11f99..a9b3aaf2ab 100644 --- a/clustered/ehcache-common-api/build.gradle +++ b/clustered/ehcache-common-api/build.gradle @@ -27,5 +27,5 @@ publishing.publications.withType(MavenPublication) { } dependencies { - api "org.terracotta:entity-common-api:$terracottaApisVersion" + api "org.terracotta:common-api:$terracottaApisVersion" } diff --git a/clustered/ehcache-common/build.gradle b/clustered/ehcache-common/build.gradle index 61a144319d..88be00e904 100644 --- a/clustered/ehcache-common/build.gradle +++ b/clustered/ehcache-common/build.gradle @@ -30,8 +30,8 @@ dependencies { api project(':ehcache-api') api project(':clustered:ehcache-common-api') - implementation "org.terracotta:entity-common-api:$terracottaApisVersion" - implementation "org.terracotta:runnel:$terracottaPlatformVersion" + implementation "org.terracotta:common-api:$terracottaApisVersion" + implementation "org.terracotta:terracotta-runnel:$terracottaPlatformVersion" implementation "org.terracotta:terracotta-utilities-tools:$terracottaUtilitiesVersion" testImplementation project(':clustered:test-utils') diff --git a/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/Store/WhitelistedUnmarshallingTest.java b/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/Store/WhitelistedUnmarshallingTest.java index 362300fb18..8716338668 100644 --- a/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/Store/WhitelistedUnmarshallingTest.java +++ b/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/Store/WhitelistedUnmarshallingTest.java @@ -62,33 +62,33 @@ private void unmarshallingStateRepoMessagesCheck(T t) { @Test public void unmarshallingIntegerTest() throws Exception { - unmarshallingStateRepoMessagesCheck(new Integer(10)); + unmarshallingStateRepoMessagesCheck(10); } @Test public void unmarshallingLongTest() throws Exception { - unmarshallingStateRepoMessagesCheck(new Long(10)); + unmarshallingStateRepoMessagesCheck(10L); } @Test public void unmarshallingFloatTest() throws Exception { - unmarshallingStateRepoMessagesCheck(new Float(10.0)); + unmarshallingStateRepoMessagesCheck(10.0f); } @Test public void unmarshallingDoubleTest() throws Exception { - unmarshallingStateRepoMessagesCheck(new Double(10.0)); + unmarshallingStateRepoMessagesCheck(10.0d); } @Test public void unmarshallingByteTest() throws Exception { byte b = 101; - unmarshallingStateRepoMessagesCheck(new Byte(b)); + unmarshallingStateRepoMessagesCheck(b); } @Test public void unmarshallingCharacterTest() throws Exception { - unmarshallingStateRepoMessagesCheck(new Character('b')); + unmarshallingStateRepoMessagesCheck('b'); } @Test @@ -98,12 +98,12 @@ public void unmarshallingStringTest() throws Exception { @Test public void unmarshallingBooleanTest() throws Exception { - unmarshallingStateRepoMessagesCheck(new Boolean(true)); + unmarshallingStateRepoMessagesCheck(true); } @Test public void unmarshallingShortTest() throws Exception { - unmarshallingStateRepoMessagesCheck(new Short((short) 1)); + unmarshallingStateRepoMessagesCheck((short) 1); } @Test diff --git a/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/messages/ChainCodecTest.java b/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/messages/ChainCodecTest.java index f22474cb4a..3b7a601309 100644 --- a/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/messages/ChainCodecTest.java +++ b/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/messages/ChainCodecTest.java @@ -115,7 +115,7 @@ public void testChainEntryWithSingleElement() { StructEncoder encoder = ChainCodec.CHAIN_ENTRY_STRUCT.encoder(); ChainCodec.encodeChainEntry(encoder, entry); - Map.Entry decoded = ChainCodec.decodeChainEntry(ChainCodec.CHAIN_ENTRY_STRUCT.decoder((ByteBuffer) encoder.encode().flip())); + Map.Entry decoded = ChainCodec.decodeChainEntry(ChainCodec.CHAIN_ENTRY_STRUCT.decoder(encoder.encode().flip())); assertThat(decoded.getKey(), is(42L)); @@ -132,7 +132,7 @@ public void testChainEntryWithSingleSequencedElement() { StructEncoder encoder = ChainCodec.CHAIN_ENTRY_STRUCT.encoder(); ChainCodec.encodeChainEntry(encoder, entry); - Map.Entry decoded = ChainCodec.decodeChainEntry(ChainCodec.CHAIN_ENTRY_STRUCT.decoder((ByteBuffer) encoder.encode().flip())); + Map.Entry decoded = ChainCodec.decodeChainEntry(ChainCodec.CHAIN_ENTRY_STRUCT.decoder(encoder.encode().flip())); assertThat(decoded.getKey(), is(43L)); assertThat(decoded.getValue().isEmpty(), is(false)); @@ -150,7 +150,7 @@ public void testChainEntryWithMultipleElements() { StructEncoder encoder = ChainCodec.CHAIN_ENTRY_STRUCT.encoder(); ChainCodec.encodeChainEntry(encoder, entry); - Map.Entry decoded = ChainCodec.decodeChainEntry(ChainCodec.CHAIN_ENTRY_STRUCT.decoder((ByteBuffer) encoder.encode().flip())); + Map.Entry decoded = ChainCodec.decodeChainEntry(ChainCodec.CHAIN_ENTRY_STRUCT.decoder(encoder.encode().flip())); assertThat(decoded.getKey(), is(44L)); assertThat(decoded.getValue().isEmpty(), is(false)); @@ -164,7 +164,7 @@ public void testChainEntryWithMultipleSequencedElements() { StructEncoder encoder = ChainCodec.CHAIN_ENTRY_STRUCT.encoder(); ChainCodec.encodeChainEntry(encoder, entry); - Map.Entry decoded = ChainCodec.decodeChainEntry(ChainCodec.CHAIN_ENTRY_STRUCT.decoder((ByteBuffer) encoder.encode().flip())); + Map.Entry decoded = ChainCodec.decodeChainEntry(ChainCodec.CHAIN_ENTRY_STRUCT.decoder(encoder.encode().flip())); assertThat(decoded.getKey(), is(45L)); assertThat(decoded.getValue().isEmpty(), is(false)); @@ -180,7 +180,7 @@ public void testEmptyChainEntry() { StructEncoder encoder = ChainCodec.CHAIN_ENTRY_STRUCT.encoder(); ChainCodec.encodeChainEntry(encoder, entry); - Map.Entry decoded = ChainCodec.decodeChainEntry(ChainCodec.CHAIN_ENTRY_STRUCT.decoder((ByteBuffer) encoder.encode().flip())); + Map.Entry decoded = ChainCodec.decodeChainEntry(ChainCodec.CHAIN_ENTRY_STRUCT.decoder(encoder.encode().flip())); assertThat(decoded.getKey(), is(46L)); assertThat(decoded.getValue().isEmpty(), is(true)); diff --git a/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/messages/ResponseCodecTest.java b/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/messages/ResponseCodecTest.java index abc8c55e68..88ca93e407 100644 --- a/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/messages/ResponseCodecTest.java +++ b/clustered/ehcache-common/src/test/java/org/ehcache/clustered/common/internal/messages/ResponseCodecTest.java @@ -76,7 +76,7 @@ public void testGetResponseCodec() { @Test public void testMapValueCodec() throws Exception { - Object subject = new Integer(10); + Object subject = 10; EhcacheEntityResponse mapValue = mapValue(subject); EhcacheEntityResponse.MapValue decoded = (EhcacheEntityResponse.MapValue) RESPONSE_CODEC.decode(RESPONSE_CODEC.encode(mapValue)); diff --git a/clustered/integration-test/build.gradle b/clustered/integration-test/build.gradle index ccf2f904c9..f9d087b29b 100644 --- a/clustered/integration-test/build.gradle +++ b/clustered/integration-test/build.gradle @@ -19,19 +19,15 @@ plugins { id 'org.ehcache.build.conventions.java' } -configurations { - serverLibs -} - dependencies { testImplementation project(':clustered:ehcache-client') testImplementation project(':clustered:ehcache-common') testImplementation project(':ehcache-impl') testImplementation project(':ehcache-xml') testImplementation project(':ehcache-107') - testImplementation "org.terracotta.internal:client-runtime:$terracottaCoreVersion" - testImplementation "org.terracotta:runnel:$terracottaPlatformVersion" - testImplementation "org.terracotta:lease-api:$terracottaPlatformVersion" + testImplementation "org.terracotta.internal:client-runtime:$terracottaRuntimeVersion" + testImplementation "org.terracotta:terracotta-runnel:$terracottaPlatformVersion" + testImplementation "org.terracotta:terracotta-lease-client:$terracottaPlatformVersion" testImplementation("javax.cache:cache-tests:$jcacheTckVersion") { exclude group:'junit', module:'junit' } @@ -40,34 +36,52 @@ dependencies { } testImplementation project(':ehcache-management') - testImplementation "org.terracotta.management:nms-entity-client:$terracottaPlatformVersion" - testImplementation "org.terracotta.management:nms-agent-entity-client:$terracottaPlatformVersion" + testImplementation "org.terracotta:terracotta-management-entities-nms-client:$terracottaPlatformVersion" + testImplementation "org.terracotta:terracotta-management-entities-nms-agent-client:$terracottaPlatformVersion" testImplementation "org.terracotta:terracotta-utilities-port-chooser:$terracottaUtilitiesVersion" - testImplementation("org.terracotta:galvan-platform-support:$terracottaPlatformVersion") { + testImplementation("org.terracotta:terracotta-dynamic-config-testing-galvan:$terracottaPlatformVersion") { exclude group: 'org.terracotta', module: 'terracotta-utilities-port-chooser' + exclude group: 'org.slf4j' + } + testImplementation("org.terracotta:terracotta-dynamic-config-cli-upgrade-tool:$terracottaPlatformVersion") { + exclude group: 'org.slf4j' } testImplementation "javax.cache:cache-api:$jcacheVersion" + testImplementation ("ch.qos.logback:logback-classic:1.2.13") { + exclude group: 'org.slf4j' + } + testImplementation("org.terracotta:terracotta-dynamic-config-cli-upgrade-tool-oss:$terracottaPlatformVersion") { + exclude group: 'org.slf4j' + } } task unzipKit(type: Sync) { dependsOn project(':clustered:ehcache-clustered').distZip from zipTree(project(':clustered:ehcache-clustered').distZip.archivePath) - into 'build/ehcache-kit' + into layout.buildDirectory.dir("ehcache-kit") } -task copyServerLibs(type: Sync) { - dependsOn unzipKit - from project.configurations.serverLibs - into "$unzipKit.destinationDir/${project(':clustered:ehcache-clustered').archivesBaseName}-$project.version-kit/server/plugins/lib" +task copyBaseKit(type: Sync) { + dependsOn(unzipKit) + from(layout.buildDirectory.dir("ehcache-kit/ehcache-clustered-${project.version}-kit/server/plugins/api")) { + into "api" + } + from(layout.buildDirectory.dir("ehcache-kit/ehcache-clustered-${project.version}-kit/server/plugins/lib")) { + into "lib" + } + destinationDir = layout.buildDirectory.dir("plugin").get().asFile } test { maxHeapSize = '512m' maxParallelForks = 8 - dependsOn copyServerLibs + dependsOn copyBaseKit environment 'JAVA_HOME', testJava.javaHome //If this directory does not exist, tests will fail with a cryptic assert failure - systemProperty 'kitInstallationPath', "$unzipKit.destinationDir/${project(':clustered:ehcache-clustered').archivesBaseName}-$project.version-kit" + systemProperty "galvan.plugin", layout.buildDirectory.dir("plugin").get().toString() + systemProperty "galvan.dir", layout.buildDirectory.dir("galvan").get().toString() + systemProperty "galvan.server", layout.buildDirectory.dir("tmp/tcserver").get().toString() +// systemProperty "serverDebugPortStart", 9000 // Uncomment to include client logging in console output // testLogging.showStandardStreams = true } @@ -81,3 +95,4 @@ configurations.all { } } } + diff --git a/clustered/integration-test/src/test/java/org/ehcache/clustered/management/ClusteringManagementServiceTest.java b/clustered/integration-test/src/test/java/org/ehcache/clustered/management/ClusteringManagementServiceTest.java index dee9b2e18d..1da96cd29c 100644 --- a/clustered/integration-test/src/test/java/org/ehcache/clustered/management/ClusteringManagementServiceTest.java +++ b/clustered/integration-test/src/test/java/org/ehcache/clustered/management/ClusteringManagementServiceTest.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.TreeSet; @@ -173,7 +174,7 @@ public void test_C_client_capabilities_exposed() throws Exception { allDescriptors.addAll(OFFHEAP_DESCRIPTORS); allDescriptors.addAll(CLUSTERED_DESCRIPTORS); - assertThat(descriptors).hasSameElementsAs(allDescriptors); + assertThat(allDescriptors).hasSameElementsAs(descriptors); } @Test @@ -202,10 +203,9 @@ public void test_D_server_capabilities_exposed() throws Exception { assertThat(tierCapabilities[3].getName()).isEqualTo("ServerStoreStatistics"); // stats - - assertThat(tierCapabilities[3].getDescriptors()).hasSameElementsAs(SERVER_STORE_DESCRIPTORS); - assertThat(managerCapabilities[2].getDescriptors()).hasSameElementsAs(POOL_DESCRIPTORS); - assertThat(tierCapabilities[1].getDescriptors()).hasSameElementsAs(POOL_DESCRIPTORS); + assertThat(new ArrayList(tierCapabilities[3].getDescriptors())).hasSameElementsAs(SERVER_STORE_DESCRIPTORS); + assertThat(new ArrayList(managerCapabilities[2].getDescriptors())).hasSameElementsAs(POOL_DESCRIPTORS); + assertThat(new ArrayList(tierCapabilities[1].getDescriptors())).hasSameElementsAs(POOL_DESCRIPTORS); // ClusterTierManagerSettings @@ -271,7 +271,7 @@ public void test_D_server_capabilities_exposed() throws Exception { assertThat(managerCapabilities[0].getDescriptors()).hasSize(3); // time + 2 resources - assertThat(managerCapabilities[1].getDescriptors()).hasSameElementsAs(OFFHEAP_RES_DESCRIPTORS); + assertThat(new ArrayList(OFFHEAP_RES_DESCRIPTORS)).hasSameElementsAs(managerCapabilities[1].getDescriptors()); } @Test diff --git a/clustered/integration-test/src/test/java/org/ehcache/clustered/replication/BasicClusteredCacheOpsReplicationMultiThreadedTest.java b/clustered/integration-test/src/test/java/org/ehcache/clustered/replication/BasicClusteredCacheOpsReplicationMultiThreadedTest.java index fc33ca4cf7..011ac675b2 100644 --- a/clustered/integration-test/src/test/java/org/ehcache/clustered/replication/BasicClusteredCacheOpsReplicationMultiThreadedTest.java +++ b/clustered/integration-test/src/test/java/org/ehcache/clustered/replication/BasicClusteredCacheOpsReplicationMultiThreadedTest.java @@ -71,6 +71,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.fail; +import org.junit.Ignore; /** @@ -99,7 +100,7 @@ public static Consistency[] data() { public Consistency cacheConsistency; @ClassRule @Rule - public static final ParallelTestCluster CLUSTER = new ParallelTestCluster(newCluster(2).in(clusterPath()) + public static final ParallelTestCluster CLUSTER = new ParallelTestCluster(newCluster(2).in(clusterPath()).inline(true) .withServiceFragment(offheapResource("primary-server-resource", 24)).build()); @Rule @@ -240,7 +241,7 @@ public void testBulkOps() throws Exception { } - @Test + @Ignore("Issue-#3278") @Test public void testClear() throws Exception { List> futures = new ArrayList<>(); Set universalSet = ConcurrentHashMap.newKeySet(); diff --git a/clustered/integration-test/src/test/java/org/ehcache/clustered/replication/BasicClusteredCacheOpsReplicationTest.java b/clustered/integration-test/src/test/java/org/ehcache/clustered/replication/BasicClusteredCacheOpsReplicationTest.java index 07b0ab19cf..a6996fe897 100644 --- a/clustered/integration-test/src/test/java/org/ehcache/clustered/replication/BasicClusteredCacheOpsReplicationTest.java +++ b/clustered/integration-test/src/test/java/org/ehcache/clustered/replication/BasicClusteredCacheOpsReplicationTest.java @@ -56,6 +56,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import org.junit.Ignore; @RunWith(ParallelParameterized.class) @@ -189,7 +190,7 @@ public void testCAS() throws Exception { }); } - @Test + @Ignore("Issue-#3278") @Test public void testClear() throws Exception { List> caches = new ArrayList<>(); diff --git a/clustered/integration-test/src/test/java/org/ehcache/clustered/util/ParallelTestCluster.java b/clustered/integration-test/src/test/java/org/ehcache/clustered/util/ParallelTestCluster.java index 9c367eb705..b3f6b7fbda 100644 --- a/clustered/integration-test/src/test/java/org/ehcache/clustered/util/ParallelTestCluster.java +++ b/clustered/integration-test/src/test/java/org/ehcache/clustered/util/ParallelTestCluster.java @@ -21,8 +21,8 @@ import org.junit.runners.model.Statement; import org.terracotta.connection.Connection; import org.terracotta.connection.ConnectionException; -import org.terracotta.passthrough.IClusterControl; import org.terracotta.testing.rules.Cluster; +import org.terracotta.testing.rules.ClusterControl; import java.net.URI; import java.util.concurrent.Phaser; @@ -32,7 +32,7 @@ public class ParallelTestCluster implements TestRule { private final Cluster cluster; - private final IClusterControl control; + private final ClusterControl control; private final AtomicReference nextTask = new AtomicReference<>(); private final Phaser membership = new Phaser() { @@ -52,8 +52,8 @@ protected boolean onAdvance(int phase, int registeredParties) { public ParallelTestCluster(Cluster cluster) { this.cluster = cluster; - IClusterControl underlyingControl = cluster.getClusterControl(); - this.control = new IClusterControl() { + ClusterControl underlyingControl = cluster.getClusterControl(); + this.control = new ClusterControl() { @Override public void waitForActive() throws Exception { underlyingControl.waitForActive(); @@ -123,7 +123,7 @@ public Connection newConnection() throws ConnectionException { return cluster.newConnection(); } - public IClusterControl getClusterControl() { + public ClusterControl getClusterControl() { return control; } @@ -155,12 +155,12 @@ public void evaluate() throws Throwable { } } - enum ClusterTask implements Consumer { - START_ONE_SERVER(IClusterControl::startOneServer), - START_ALL_SERVERS(IClusterControl::startAllServers), - TERMINATE_ACTIVE(IClusterControl::terminateActive), - TERMINATE_ONE_PASSIVE(IClusterControl::terminateOnePassive), - TERMINATE_ALL_SERVERS(IClusterControl::terminateAllServers); + enum ClusterTask implements Consumer { + START_ONE_SERVER(ClusterControl::startOneServer), + START_ALL_SERVERS(ClusterControl::startAllServers), + TERMINATE_ACTIVE(ClusterControl::terminateActive), + TERMINATE_ONE_PASSIVE(ClusterControl::terminateOnePassive), + TERMINATE_ALL_SERVERS(ClusterControl::terminateAllServers); private final Task task; @@ -168,7 +168,7 @@ enum ClusterTask implements Consumer { this.task = task; } - public void accept(IClusterControl control) { + public void accept(ClusterControl control) { try { task.run(control); } catch (Exception e) { @@ -177,7 +177,7 @@ public void accept(IClusterControl control) { } interface Task { - void run(IClusterControl control) throws Exception; + void run(ClusterControl control) throws Exception; } } } diff --git a/clustered/integration-test/src/test/java/org/ehcache/testing/ConfigFileStartupBuilder.java b/clustered/integration-test/src/test/java/org/ehcache/testing/ConfigFileStartupBuilder.java new file mode 100644 index 0000000000..9fe0312caf --- /dev/null +++ b/clustered/integration-test/src/test/java/org/ehcache/testing/ConfigFileStartupBuilder.java @@ -0,0 +1,131 @@ +/* + * Copyright Terracotta, Inc. + * Copyright IBM Corp. 2024, 2025 + * + * 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.ehcache.testing; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.terracotta.dynamic_config.cli.upgrade_tools.config_converter.ConfigConverterTool; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import org.terracotta.testing.config.ConfigConstants; +import org.terracotta.testing.config.DefaultStartupCommandBuilder; +import org.terracotta.testing.config.StartupCommandBuilder; + +public class ConfigFileStartupBuilder extends DefaultStartupCommandBuilder { + private String[] builtCommand; + private int stripeId; + private String clusterName = ConfigConstants.DEFAULT_CLUSTER_NAME; + + public ConfigFileStartupBuilder() { + } + + @Override + public StartupCommandBuilder stripeName(String stripeName) { + super.stripeName(stripeName); + this.stripeId = Integer.parseInt(stripeName.substring(6)); + return this; + } + + public int getStripeId() { + return stripeId; + } + + public String getClusterName() { + return clusterName; + } + + @Override + public String[] build() { + if (builtCommand == null) { + try { + Path tcConfig = installServer(); + Path configDir = convertToConfigFile(tcConfig, true).resolve("test.properties"); + builtCommand = configFileStartupCommand(configDir); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + return builtCommand.clone(); + } + + private String[] configFileStartupCommand(Path configFile) { + List command = new ArrayList<>(); + + command.add("-f"); + command.add(configFile.toString()); + + command.add("-n"); + command.add(getServerName()); + + command.add("--auto-activate"); + + Path configDir; + try { + configDir = Files.createTempDirectory(getServerWorkingDir(), "config"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + command.add("-r"); + command.add(configDir.toString()); + + return command.toArray(String[]::new); + } + + @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") + protected Path convertToConfigFile(Path tcConfig, boolean properties) { + Path generatedConfigFileDir = getServerWorkingDir().getParent().resolve("generated-configs"); + + if (Files.exists(generatedConfigFileDir)) { + // this builder is called for each server, but the CLI will generate the config directories for all. + return generatedConfigFileDir; + } + + List command = new ArrayList<>(); + command.add("convert"); + + command.add("-c"); + command.add(tcConfig.toString()); + + for (int i = 0; i < 1; i++) { + command.add("-s"); + command.add("stripe[" + i + "]"); + } + if (properties) { + command.add("-t"); + command.add("properties"); + } + + command.add("-d"); + command.add(generatedConfigFileDir.toString()); + + command.add("-f"); //Do not fail for relative paths + + command.add("-n"); + command.add(getClusterName()); + + executeCommand(command); + return generatedConfigFileDir; + } + + protected static void executeCommand(List command) { + new ConfigConverterTool().run(command.toArray(String[]::new)); + } +} diff --git a/clustered/integration-test/src/test/java/org/ehcache/testing/ConfigRepoStartupBuilder.java b/clustered/integration-test/src/test/java/org/ehcache/testing/ConfigRepoStartupBuilder.java new file mode 100644 index 0000000000..044035fb8f --- /dev/null +++ b/clustered/integration-test/src/test/java/org/ehcache/testing/ConfigRepoStartupBuilder.java @@ -0,0 +1,62 @@ +/* + * Copyright Terracotta, Inc. + * Copyright IBM Corp. 2024, 2025 + * + * 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.ehcache.testing; + + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class ConfigRepoStartupBuilder extends ConfigFileStartupBuilder { + private String[] builtCommand; + + public ConfigRepoStartupBuilder() { + } + + @Override + public String[] build() { + if (builtCommand == null) { + try { + Path tcConfig = installServer(); + Path generatedRepositories = convertToConfigFile(tcConfig, false); + + // moves the generated files onto the server folder, but only for this server we are building + Path source = generatedRepositories.resolve("stripe-" + getStripeId()).resolve(getServerName()).toAbsolutePath(); + Path destination = getServerWorkingDir().resolve("config").toAbsolutePath(); + org.terracotta.utilities.io.Files.relocate(source, destination); + buildStartupCommand(destination); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + return builtCommand.clone(); + } + + private void buildStartupCommand(Path destination) { + List command = new ArrayList<>(); + + if (isConsistentStartup()) { + command.add("-c"); + } + + command.add("-r"); + command.add(destination.toAbsolutePath().toString()); + builtCommand = command.toArray(new String[0]); + } +} diff --git a/clustered/integration-test/src/test/java/org/ehcache/testing/StandardCluster.java b/clustered/integration-test/src/test/java/org/ehcache/testing/StandardCluster.java index 476fcbb8f5..924ed7dd93 100644 --- a/clustered/integration-test/src/test/java/org/ehcache/testing/StandardCluster.java +++ b/clustered/integration-test/src/test/java/org/ehcache/testing/StandardCluster.java @@ -16,7 +16,6 @@ */ package org.ehcache.testing; -import org.terracotta.testing.config.ConfigRepoStartupBuilder; import org.terracotta.testing.rules.BasicExternalClusterBuilder; import java.nio.file.Path; diff --git a/clustered/integration-test/src/test/resources/simplelogger.properties b/clustered/integration-test/src/test/resources/simplelogger.properties index 38ac27b913..376dbeb7e2 100644 --- a/clustered/integration-test/src/test/resources/simplelogger.properties +++ b/clustered/integration-test/src/test/resources/simplelogger.properties @@ -17,3 +17,4 @@ org.slf4j.simpleLogger.showDateTime=true org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss.SSS +org.slf4j.simpleLogger.log.org.ehcache.clustered.management=debug \ No newline at end of file diff --git a/clustered/server/ehcache-entity/build.gradle b/clustered/server/ehcache-entity/build.gradle index 3834655e2d..2f6252635a 100644 --- a/clustered/server/ehcache-entity/build.gradle +++ b/clustered/server/ehcache-entity/build.gradle @@ -28,14 +28,21 @@ publishing.publications.withType(MavenPublication) { dependencies { service project(':clustered:server:ehcache-service-api') - service "org.terracotta.management:monitoring-service-api:$terracottaPlatformVersion" - service "org.terracotta.management:management-registry:$terracottaPlatformVersion" + service "org.terracotta:terracotta-management-server-api:$terracottaPlatformVersion" + service "org.terracotta:terracotta-management-registry:$terracottaPlatformVersion" api project(':clustered:ehcache-common') - implementation "org.terracotta:runnel:$terracottaPlatformVersion" + implementation "org.terracotta:terracotta-runnel:$terracottaPlatformVersion" implementation "org.terracotta:offheap-store:$offheapVersion" - implementation "org.terracotta:client-message-tracker:$terracottaPlatformVersion" + implementation "org.terracotta:terracotta-client-message-tracker:$terracottaPlatformVersion" testImplementation project(':clustered:server:ehcache-service') testImplementation project(':clustered:test-utils') + testImplementation ("org.terracotta:passthrough-server:$terracottaRuntimeVersion") { + exclude group:'org.slf4j' + } + + testImplementation project(':clustered:server:ehcache-service-api') + testImplementation "org.terracotta:terracotta-resources-offheap:$terracottaPlatformVersion" + testImplementation "org.terracotta:statistics:$statisticVersion" } diff --git a/clustered/server/ehcache-service/build.gradle b/clustered/server/ehcache-service/build.gradle index a2e1031993..5cdc377406 100644 --- a/clustered/server/ehcache-service/build.gradle +++ b/clustered/server/ehcache-service/build.gradle @@ -28,13 +28,15 @@ publishing.publications.withType(MavenPublication) { dependencies { service project(':clustered:server:ehcache-service-api') - service "org.terracotta:offheap-resource:$terracottaPlatformVersion" + service "org.terracotta:terracotta-resources-offheap:$terracottaPlatformVersion" service "org.terracotta:statistics:$statisticVersion" implementation project(':clustered:ehcache-common') implementation "org.terracotta:offheap-store:$offheapVersion" testImplementation project(':clustered:test-utils') - testImplementation "org.terracotta.management:monitoring-service-api:$terracottaPlatformVersion" - testImplementation "org.terracotta:passthrough-server:$terracottaPassthroughTestingVersion" + testImplementation "org.terracotta:terracotta-management-server-api:$terracottaPlatformVersion" + testImplementation ("org.terracotta:passthrough-server:$terracottaRuntimeVersion") { + exclude group: "ch.qos.logback" + } } diff --git a/clustered/server/ehcache-service/src/main/java/org/ehcache/clustered/server/offheap/OffHeapChainStorageEngine.java b/clustered/server/ehcache-service/src/main/java/org/ehcache/clustered/server/offheap/OffHeapChainStorageEngine.java index 527978652f..6a29355de4 100644 --- a/clustered/server/ehcache-service/src/main/java/org/ehcache/clustered/server/offheap/OffHeapChainStorageEngine.java +++ b/clustered/server/ehcache-service/src/main/java/org/ehcache/clustered/server/offheap/OffHeapChainStorageEngine.java @@ -215,7 +215,7 @@ public ByteBuffer readBinaryValue(long chain) { detachedContiguousBuffer.reset(); element = storage.readLong(element + ELEMENT_HEADER_NEXT_OFFSET); } while (element != chain); - return (ByteBuffer)detachedContiguousBuffer.flip(); + return detachedContiguousBuffer.flip(); } @Override @@ -700,7 +700,7 @@ private void append(long head, long tail) { } private Element element(ByteBuffer attachedBuffer, final long sequence) { - final ByteBuffer detachedBuffer = (ByteBuffer) ByteBuffer.allocate(attachedBuffer.remaining()).put(attachedBuffer).flip(); + final ByteBuffer detachedBuffer = ByteBuffer.allocate(attachedBuffer.remaining()).put(attachedBuffer).flip(); return new SequencedElement() { diff --git a/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/offheap/ChainMapExtensionTest.java b/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/offheap/ChainMapExtensionTest.java index fe0ceba783..65a615fba9 100644 --- a/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/offheap/ChainMapExtensionTest.java +++ b/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/offheap/ChainMapExtensionTest.java @@ -144,7 +144,7 @@ private static ByteBuffer buffer(int i) { while (buffer.hasRemaining()) { buffer.put((byte) i); } - return (ByteBuffer) buffer.flip(); + return buffer.flip(); } private static Matcher element(int i) { diff --git a/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/offheap/ChainMapTest.java b/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/offheap/ChainMapTest.java index 32c87c4ccf..c57977037b 100644 --- a/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/offheap/ChainMapTest.java +++ b/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/offheap/ChainMapTest.java @@ -504,7 +504,7 @@ private static ByteBuffer buffer(int i) { while (buffer.hasRemaining()) { buffer.put((byte) i); } - return (ByteBuffer) buffer.flip(); + return buffer.flip(); } private static Matcher element(final int i) { diff --git a/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/state/EhcacheStateServiceProviderTest.java b/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/state/EhcacheStateServiceProviderTest.java index cb6613a456..d98758ba36 100644 --- a/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/state/EhcacheStateServiceProviderTest.java +++ b/clustered/server/ehcache-service/src/test/java/org/ehcache/clustered/server/state/EhcacheStateServiceProviderTest.java @@ -26,14 +26,10 @@ import org.terracotta.entity.PlatformConfiguration; import org.terracotta.entity.ServiceProviderCleanupException; import org.terracotta.entity.ServiceProviderConfiguration; +import org.terracotta.offheapresource.OffHeapResourceIdentifier; import org.terracotta.offheapresource.OffHeapResources; import org.terracotta.offheapresource.OffHeapResourcesProvider; -import org.terracotta.offheapresource.config.MemoryUnit; -import org.terracotta.offheapresource.config.OffheapResourcesType; -import org.terracotta.offheapresource.config.ResourceType; -import java.math.BigInteger; -import java.util.Collection; import java.util.Collections; import static java.util.Collections.emptyMap; @@ -57,14 +53,8 @@ public class EhcacheStateServiceProviderTest { @Before public void setUp() { - ResourceType resource = new ResourceType(); - resource.setName("primary"); - resource.setUnit(MemoryUnit.MB); - resource.setValue(BigInteger.valueOf(4L)); - OffheapResourcesType configuration = new OffheapResourcesType(); - configuration.getResource().add(resource); - OffHeapResources offheapResources = new OffHeapResourcesProvider(configuration); - + OffHeapResourcesProvider offheapResources = new OffHeapResourcesProvider(emptyMap()); + offheapResources.addOffHeapResource(OffHeapResourceIdentifier.identifier("primary"), 4 * 1024 * 1024); platformConfiguration = mock(PlatformConfiguration.class); when(platformConfiguration.getExtendedConfiguration(OffHeapResources.class)).thenReturn(Collections.singletonList(offheapResources)); serviceProviderConfiguration = mock(ServiceProviderConfiguration.class); diff --git a/demos/build.gradle b/demos/build.gradle index 6b4e163021..b84e1bfb38 100644 --- a/demos/build.gradle +++ b/demos/build.gradle @@ -16,7 +16,7 @@ subprojects { dependencies { implementation project(':ehcache-impl') implementation 'javax.servlet:javax.servlet-api:3.1.0' - runtimeOnly 'ch.qos.logback:logback-classic:1.2.11' + runtimeOnly 'ch.qos.logback:logback-classic:1.2.13' runtimeOnly 'com.h2database:h2:1.4.196' } diff --git a/ehcache-107/src/test/java/org/ehcache/ParsesConfigurationExtensionTest.java b/ehcache-107/src/test/java/org/ehcache/ParsesConfigurationExtensionTest.java index 8284d9a797..7e9c1eb838 100644 --- a/ehcache-107/src/test/java/org/ehcache/ParsesConfigurationExtensionTest.java +++ b/ehcache-107/src/test/java/org/ehcache/ParsesConfigurationExtensionTest.java @@ -53,7 +53,7 @@ public class ParsesConfigurationExtensionTest { @Test - public void testConfigParse() throws ClassNotFoundException, SAXException, InstantiationException, IllegalAccessException, IOException { + public void testConfigParse() throws SAXException, ReflectiveOperationException, IOException { final XmlConfiguration configuration = new XmlConfiguration(this.getClass().getResource("/ehcache-107.xml")); final DefaultJsr107Service jsr107Service = new DefaultJsr107Service(ServiceUtils.findSingletonAmongst(Jsr107Configuration.class, configuration.getServiceCreationConfigurations())); @@ -64,9 +64,8 @@ public void testConfigParse() throws ClassNotFoundException, SAXException, Insta assertThat(jsr107Service.getTemplateNameForCache("bars"), equalTo("tinyCache")); } - @SuppressWarnings("rawtypes") @Test - public void testXmlExample() throws ClassNotFoundException, SAXException, InstantiationException, IOException, IllegalAccessException { + public void testXmlExample() throws SAXException, ReflectiveOperationException, IOException { XmlConfiguration config = new XmlConfiguration(ParsesConfigurationExtensionTest.class.getResource("/ehcache-example.xml")); final DefaultJsr107Service jsr107Service = new DefaultJsr107Service(ServiceUtils.findSingletonAmongst(Jsr107Configuration.class, config.getServiceCreationConfigurations())); diff --git a/ehcache-core/src/main/java/org/ehcache/core/util/ClassLoading.java b/ehcache-core/src/main/java/org/ehcache/core/util/ClassLoading.java index b8fb8a22e4..d33f3b507a 100644 --- a/ehcache-core/src/main/java/org/ehcache/core/util/ClassLoading.java +++ b/ehcache-core/src/main/java/org/ehcache/core/util/ClassLoading.java @@ -30,7 +30,6 @@ import java.util.ServiceLoader; import java.util.function.Supplier; -import static java.security.AccessController.doPrivileged; import static java.util.Collections.enumeration; import static java.util.Collections.list; import static java.util.stream.Collectors.toList; @@ -57,14 +56,14 @@ public static Iterable servicesOfType(Class serviceType) { } } - @SuppressWarnings("unchecked") + @SuppressWarnings("removal") public static ClassLoader delegationChain(Supplier loader, ClassLoader ... loaders) { - return doPrivileged((PrivilegedAction) () -> new ChainedClassLoader(concat(of(loader), of(loaders).map(l -> () -> l)).collect(toList()))); + return java.security.AccessController.doPrivileged((PrivilegedAction) () -> new ChainedClassLoader(concat(of(loader), of(loaders).map(l -> () -> l)).collect(toList()))); } - @SuppressWarnings("unchecked") + @SuppressWarnings("removal") public static ClassLoader delegationChain(ClassLoader ... loaders) { - return doPrivileged((PrivilegedAction) () -> new ChainedClassLoader(of(loaders).>map(l -> () -> l).collect(toList()))); + return java.security.AccessController.doPrivileged((PrivilegedAction) () -> new ChainedClassLoader(of(loaders).>map(l -> () -> l).collect(toList()))); } private static class ChainedClassLoader extends ClassLoader { diff --git a/ehcache-impl/build.gradle b/ehcache-impl/build.gradle index 4d50e71ef7..a76a37637c 100644 --- a/ehcache-impl/build.gradle +++ b/ehcache-impl/build.gradle @@ -57,8 +57,8 @@ dependencies { compileOnly 'org.osgi:org.osgi.service.component.annotations:1.3.0' testImplementation testFixtures(project(':ehcache-core')) testImplementation project(':core-spi-test') - testImplementation 'org.ow2.asm:asm:6.2' - testImplementation 'org.ow2.asm:asm-commons:6.2' + testImplementation 'org.ow2.asm:asm:9.7.1' + testImplementation 'org.ow2.asm:asm-commons:9.7.1' testImplementation ("org.terracotta:statistics:$parent.statisticVersion") } @@ -75,3 +75,14 @@ compileUnsafe { //no -Werror due to unsafe options.compilerArgs = ['-Xlint:all'] } +/* +tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach { + classes = classes.filter { + !it.path.contains('org/ehcache/impl/internal/classes/commonslang/') + } +} +*/ +tasks.named("spotbugsMain", com.github.spotbugs.snom.SpotBugsTask) { + // Configure SpotBugs at the extension level + excludeFilter = file("config/spotbugs/exclude-commonslang.xml") +} \ No newline at end of file diff --git a/ehcache-impl/config/spotbugs/exclude-commonslang.xml b/ehcache-impl/config/spotbugs/exclude-commonslang.xml new file mode 100644 index 0000000000..5b33b8a35a --- /dev/null +++ b/ehcache-impl/config/spotbugs/exclude-commonslang.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/ArrayUtils.java b/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/ArrayUtils.java index a4487ae4e6..be368014d5 100644 --- a/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/ArrayUtils.java +++ b/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/ArrayUtils.java @@ -22,230 +22,7446 @@ package org.ehcache.impl.internal.classes.commonslang; import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; /** - *

Operations on arrays, primitive arrays (like {@code int[]}) and + * Operations on arrays, primitive arrays (like {@code int[]}) and * primitive wrapper arrays (like {@code Integer[]}). - * - *

This class tries to handle {@code null} input gracefully. + *

+ * 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# + * element may throw an exception. Each method documents its behavior. + *

+ *

+ * #ThreadSafe# + *

* @since 2.0 */ public class ArrayUtils { /** - * An empty immutable {@code Object} array. + * An empty immutable {@code boolean} array. + */ + public static final boolean[] EMPTY_BOOLEAN_ARRAY = {}; + + /** + * An empty immutable {@link Boolean} array. */ - public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + public static final Boolean[] EMPTY_BOOLEAN_OBJECT_ARRAY = {}; + /** - * An empty immutable {@code Class} array. + * An empty immutable {@code byte} array. */ - public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + public static final byte[] EMPTY_BYTE_ARRAY = {}; + /** - * An empty immutable {@code long} array. + * An empty immutable {@link Byte} array. + */ + public static final Byte[] EMPTY_BYTE_OBJECT_ARRAY = {}; + + /** + * An empty immutable {@code char} array. + */ + public static final char[] EMPTY_CHAR_ARRAY = {}; + + /** + * An empty immutable {@link Character} array. + */ + public static final Character[] EMPTY_CHARACTER_OBJECT_ARRAY = {}; + + /** + * An empty immutable {@link Class} array. + */ + public static final Class[] EMPTY_CLASS_ARRAY = {}; + + /** + * An empty immutable {@code double} array. + */ + public static final double[] EMPTY_DOUBLE_ARRAY = {}; + + /** + * An empty immutable {@link Double} array. + */ + public static final Double[] EMPTY_DOUBLE_OBJECT_ARRAY = {}; + + /** + * An empty immutable {@link Field} array. + * + * @since 3.10 + */ + public static final Field[] EMPTY_FIELD_ARRAY = {}; + + /** + * An empty immutable {@code float} array. */ - public static final long[] EMPTY_LONG_ARRAY = new long[0]; + public static final float[] EMPTY_FLOAT_ARRAY = {}; + + /** + * An empty immutable {@link Float} array. + */ + public static final Float[] EMPTY_FLOAT_OBJECT_ARRAY = {}; + /** * An empty immutable {@code int} array. */ - public static final int[] EMPTY_INT_ARRAY = new int[0]; + public static final int[] EMPTY_INT_ARRAY = {}; + + /** + * An empty immutable {@link Integer} array. + */ + public static final Integer[] EMPTY_INTEGER_OBJECT_ARRAY = {}; + + /** + * An empty immutable {@code long} array. + */ + public static final long[] EMPTY_LONG_ARRAY = {}; + + /** + * An empty immutable {@link Long} array. + */ + public static final Long[] EMPTY_LONG_OBJECT_ARRAY = {}; + + /** + * An empty immutable {@link Method} array. + * + * @since 3.10 + */ + public static final Method[] EMPTY_METHOD_ARRAY = {}; + + /** + * An empty immutable {@link Object} array. + */ + public static final Object[] EMPTY_OBJECT_ARRAY = {}; + /** * An empty immutable {@code short} array. */ - public static final short[] EMPTY_SHORT_ARRAY = new short[0]; + public static final short[] EMPTY_SHORT_ARRAY = {}; + + /** + * An empty immutable {@link Short} array. + */ + public static final Short[] EMPTY_SHORT_OBJECT_ARRAY = {}; + + /** + * An empty immutable {@link String} array. + */ + public static final String[] EMPTY_STRING_ARRAY = {}; + + /** + * An empty immutable {@link Throwable} array. + * + * @since 3.10 + */ + public static final Throwable[] EMPTY_THROWABLE_ARRAY = {}; + + /** + * An empty immutable {@link Type} array. + * + * @since 3.10 + */ + public static final Type[] EMPTY_TYPE_ARRAY = {}; + + /** + * 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; + + /** + * The {@code SOFT_MAX_ARRAY_LENGTH} constant from Java's internal ArraySupport class. + * + * @since 3.19.0 + */ + public static int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; + + /** + * 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; + } + + /** + * 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)      = IllegalArgumentException
+     * 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 + * @throws IllegalArgumentException if both arguments are null + * @since 2.1 + */ + public static T[] add(final T[] array, final T element) { + final 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; + } + + /** + * 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([], [])         = []
+     * ArrayUtils.addAll(null, null)     = null
+     * 
+ * + * @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 or {@code null}. + * @since 2.1 + */ + public static boolean[] addAll(final boolean[] array1, final boolean... array2) { + if (array1 == null) { + return clone(array2); + } + 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([], [])         = []
+     * ArrayUtils.addAll(null, null)     = null
+     * 
+ * + * @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 or {@code null}. + * @since 2.1 + */ + public static byte[] addAll(final byte[] array1, final byte... array2) { + if (array1 == null) { + return clone(array2); + } + 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([], [])         = []
+     * ArrayUtils.addAll(null, null)     = null
+     * 
+ * + * @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 or {@code null}. + * @since 2.1 + */ + public static char[] addAll(final char[] array1, final char... array2) { + if (array1 == null) { + return clone(array2); + } + 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([], [])         = []
+     * ArrayUtils.addAll(null, null)     = null
+     * 
+ * + * @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 or {@code null}. + * @since 2.1 + */ + public static double[] addAll(final double[] array1, final double... array2) { + if (array1 == null) { + return clone(array2); + } + 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; + } + + /** + * 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([], [])         = []
+     * ArrayUtils.addAll(null, null)     = null
+     * 
+ * + * @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 or {@code null}. + * @since 2.1 + */ + public static float[] addAll(final float[] array1, final float... array2) { + if (array1 == null) { + return clone(array2); + } + 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([], [])         = []
+     * ArrayUtils.addAll(null, null)     = null
+     * 
+ * + * @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 or {@code null}. + * @since 2.1 + */ + public static int[] addAll(final int[] array1, final int... array2) { + if (array1 == null) { + return clone(array2); + } + 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([], [])         = []
+     * ArrayUtils.addAll(null, null)     = null
+     * 
+ * + * @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 or {@code null}. + * @since 2.1 + */ + public static long[] addAll(final long[] array1, final long... array2) { + if (array1 == null) { + return clone(array2); + } + 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([], [])         = []
+     * ArrayUtils.addAll(null, null)     = null
+     * 
+ * + * @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 or {@code null}. + * @since 2.1 + */ + public static short[] addAll(final short[] array1, final short... array2) { + if (array1 == null) { + return clone(array2); + } + 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; + } + + /** + * Copies the given array and adds the given element at the beginning of the new array. + *

+ * The new array contains the same elements of the input array plus the given element in the first 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.addFirst(null, true)          = [true]
+     * ArrayUtils.addFirst([true], false)       = [false, true]
+     * ArrayUtils.addFirst([true, false], true) = [true, true, false]
+     * 
+ * + * @param array the array to "add" the element to, may be {@code null}. + * @param element the object to add. + * @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. + * @since 3.10 + */ + public static boolean[] addFirst(final boolean[] array, final boolean element) { + return array == null ? add(array, element) : insert(0, array, element); + } + + /** + * Copies the given array and adds the given element at the beginning of the new array. + *

+ * The new array contains the same elements of the input array plus the given element in the first 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.addFirst(null, 1)   = [1]
+     * ArrayUtils.addFirst([1], 0)    = [0, 1]
+     * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+     * 
+ * + * @param array the array to "add" the element to, may be {@code null}. + * @param element the object to add. + * @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. + * @since 3.10 + */ + public static byte[] addFirst(final byte[] array, final byte element) { + return array == null ? add(array, element) : insert(0, array, element); + } + + /** + * Copies the given array and adds the given element at the beginning of the new array. + *

+ * The new array contains the same elements of the input array plus the given element in the first 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.addFirst(null, '1')       = ['1']
+     * ArrayUtils.addFirst(['1'], '0')      = ['0', '1']
+     * ArrayUtils.addFirst(['1', '0'], '1') = ['1', '1', '0']
+     * 
+ * + * @param array the array to "add" the element to, may be {@code null}. + * @param element the object to add. + * @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. + * @since 3.10 + */ + public static char[] addFirst(final char[] array, final char element) { + return array == null ? add(array, element) : insert(0, array, element); + } + + /** + * Copies the given array and adds the given element at the beginning of the new array. + *

+ * The new array contains the same elements of the input array plus the given element in the first 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.addFirst(null, 1)   = [1]
+     * ArrayUtils.addFirst([1], 0)    = [0, 1]
+     * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+     * 
+ * + * @param array the array to "add" the element to, may be {@code null}. + * @param element the object to add. + * @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. + * @since 3.10 + */ + public static double[] addFirst(final double[] array, final double element) { + return array == null ? add(array, element) : insert(0, array, element); + } + + /** + * Copies the given array and adds the given element at the beginning of the new array. + *

+ * The new array contains the same elements of the input array plus the given element in the first 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.addFirst(null, 1)   = [1]
+     * ArrayUtils.addFirst([1], 0)    = [0, 1]
+     * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+     * 
+ * + * @param array the array to "add" the element to, may be {@code null}. + * @param element the object to add. + * @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. + * @since 3.10 + */ + public static float[] addFirst(final float[] array, final float element) { + return array == null ? add(array, element) : insert(0, array, element); + } + + /** + * Copies the given array and adds the given element at the beginning of the new array. + *

+ * The new array contains the same elements of the input array plus the given element in the first 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.addFirst(null, 1)   = [1]
+     * ArrayUtils.addFirst([1], 0)    = [0, 1]
+     * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+     * 
+ * + * @param array the array to "add" the element to, may be {@code null}. + * @param element the object to add. + * @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. + * @since 3.10 + */ + public static int[] addFirst(final int[] array, final int element) { + return array == null ? add(array, element) : insert(0, array, element); + } + + /** + * Copies the given array and adds the given element at the beginning of the new array. + *

+ * The new array contains the same elements of the input array plus the given element in the first 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.addFirst(null, 1)   = [1]
+     * ArrayUtils.addFirst([1], 0)    = [0, 1]
+     * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+     * 
+ * + * @param array the array to "add" the element to, may be {@code null}. + * @param element the object to add. + * @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. + * @since 3.10 + */ + public static long[] addFirst(final long[] array, final long element) { + return array == null ? add(array, element) : insert(0, array, element); + } + + /** + * Copies the given array and adds the given element at the beginning of the new array. + *

+ * The new array contains the same elements of the input array plus the given element in the first 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.addFirst(null, 1)   = [1]
+     * ArrayUtils.addFirst([1], 0)    = [0, 1]
+     * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+     * 
+ * + * @param array the array to "add" the element to, may be {@code null}. + * @param element the object to add. + * @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. + * @since 3.10 + */ + public static short[] addFirst(final short[] array, final short element) { + return array == null ? add(array, element) : insert(0, array, element); + } + + /** + * A fluent version of {@link System#arraycopy(Object, int, Object, int, int)} that returns the destination array. + * + * @param the type. + * @param source the source array. + * @param sourcePos starting position in the source array. + * @param destPos starting position in the destination data. + * @param length the number of array elements to be copied. + * @param allocator allocates the array to populate and return. + * @return dest + * @throws IndexOutOfBoundsException if copying would cause access of data outside array bounds. + * @throws ArrayStoreException if an element in the {@code src} array could not be stored into the {@code dest} array because of a type + * mismatch. + * @throws NullPointerException if either {@code src} or {@code dest} is {@code null}. + * @since 3.15.0 + */ + public static T arraycopy(final T source, final int sourcePos, final int destPos, final int length, final Function allocator) { + return arraycopy(source, sourcePos, allocator.apply(length), destPos, length); + } + + /** + * A fluent version of {@link System#arraycopy(Object, int, Object, int, int)} that returns the destination array. + * + * @param the type. + * @param source the source array. + * @param sourcePos starting position in the source array. + * @param destPos starting position in the destination data. + * @param length the number of array elements to be copied. + * @param allocator allocates the array to populate and return. + * @return dest + * @throws IndexOutOfBoundsException if copying would cause access of data outside array bounds. + * @throws ArrayStoreException if an element in the {@code src} array could not be stored into the {@code dest} array because of a type + * mismatch. + * @throws NullPointerException if either {@code src} or {@code dest} is {@code null}. + * @since 3.15.0 + */ + public static T arraycopy(final T source, final int sourcePos, final int destPos, final int length, final Supplier allocator) { + return arraycopy(source, sourcePos, allocator.get(), destPos, length); + } + + /** + * A fluent version of {@link System#arraycopy(Object, int, Object, int, int)} that returns the destination array. + * + * @param the type + * @param source the source array. + * @param sourcePos starting position in the source array. + * @param dest the destination array. + * @param destPos starting position in the destination data. + * @param length the number of array elements to be copied. + * @return dest + * @throws IndexOutOfBoundsException if copying would cause access of data outside array bounds. + * @throws ArrayStoreException if an element in the {@code src} array could not be stored into the {@code dest} array because of a type + * mismatch. + * @throws NullPointerException if either {@code src} or {@code dest} is {@code null}. + * @since 3.15.0 + */ + public static T arraycopy(final T source, final int sourcePos, final T dest, final int destPos, final int length) { + System.arraycopy(source, sourcePos, dest, destPos, length); + return dest; + } + + /** + * Clones an array or returns {@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) { + return array != null ? array.clone() : null; + } + + /** + * Clones an array or returns {@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) { + return array != null ? array.clone() : null; + } + + /** + * Clones an array or returns {@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) { + return array != null ? array.clone() : null; + } + + /** + * Clones an array or returns {@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) { + return array != null ? array.clone() : null; + } + + /** + * Clones an array or returns {@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) { + return array != null ? array.clone() : null; + } + + /** + * Clones an array or returns {@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) { + return array != null ? array.clone() : null; + } + + /** + * Clones an array or returns {@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) { + return array != null ? array.clone() : null; + } + + /** + * Clones an array or returns {@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) { + return array != null ? array.clone() : null; + } + + /** + * Shallow clones an array or returns {@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) { + return array != null ? array.clone() : null; + } + + /** + * 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 + * @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; + } + + /** + * Checks if the value is in the given array. + *

+ * The method returns {@code false} if a {@code null} array is passed in. + *

+ *

+ * If the {@code array} elements you are searching implement {@link Comparator}, consider whether it is worth using + * {@link Arrays#sort(byte[])} and {@link Arrays#binarySearch(byte[], byte)}. + *

+ * + * @param array the array to search + * @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; + } + + /** + * Checks if the value is in the given array. + *

+ * The method returns {@code false} if a {@code null} array is passed in. + *

+ *

+ * If the {@code array} elements you are searching implement {@link Comparator}, consider whether it is worth using + * {@link Arrays#sort(char[])} and {@link Arrays#binarySearch(char[], char)}. + *

+ * + * @param array the array to search + * @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; + } + + /** + * Checks if the value is in the given array. + *

+ * The method returns {@code false} if a {@code null} array is passed in. + *

+ *

+ * If the {@code array} elements you are searching implement {@link Comparator}, consider whether it is worth using + * {@link Arrays#sort(double[])} and {@link Arrays#binarySearch(double[], double)}. + *

+ * + * @param array the array to search + * @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. + *

+ *

+ * If the {@code array} elements you are searching implement {@link Comparator}, consider whether it is worth using + * {@link Arrays#sort(double[])} and {@link Arrays#binarySearch(double[], double)}. + *

+ * + * @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; + } + + /** + * Checks if the value is in the given array. + *

+ * The method returns {@code false} if a {@code null} array is passed in. + *

+ *

+ * If the {@code array} elements you are searching implement {@link Comparator}, consider whether it is worth using + * {@link Arrays#sort(float[])} and {@link Arrays#binarySearch(float[], float)}. + *

+ * + * @param array the array to search + * @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; + } + + /** + * Checks if the value is in the given array. + *

+ * The method returns {@code false} if a {@code null} array is passed in. + *

+ *

+ * If the {@code array} elements you are searching implement {@link Comparator}, consider whether it is worth using + * {@link Arrays#sort(int[])} and {@link Arrays#binarySearch(int[], int)}. + *

+ * + * @param array the array to search + * @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; + } + + /** + * Checks if the value is in the given array. + *

+ * The method returns {@code false} if a {@code null} array is passed in. + *

+ *

+ * If the {@code array} elements you are searching implement {@link Comparator}, consider whether it is worth using + * {@link Arrays#sort(long[])} and {@link Arrays#binarySearch(long[], long)}. + *

+ * + * @param array the array to search + * @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; + } + + /** + * Checks if the object is in the given array. + *

+ * The method returns {@code false} if a {@code null} array is passed in. + *

+ *

+ * If the {@code array} elements you are searching implement {@link Comparator}, consider whether it is worth using + * {@link Arrays#sort(Object[], Comparator)} and {@link Arrays#binarySearch(Object[], Object)}. + *

+ * + * @param array the array to search, may be {@code null}. + * @param objectToFind the object to find, may be {@code null}. + * @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; + } + + /** + * Checks if the value is in the given array. + *

+ * The method returns {@code false} if a {@code null} array is passed in. + *

+ *

+ * If the {@code array} elements you are searching implement {@link Comparator}, consider whether it is worth using + * {@link Arrays#sort(short[])} and {@link Arrays#binarySearch(short[], short)}. + *

+ * + * @param array the array to search + * @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; + } + + /** + * 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); + } + + /** + * Gets the nTh element of an array or null if the index is out of bounds or the array is null. + * + * @param The type of array elements. + * @param array The array to index. + * @param index The index + * @return the nTh element of an array or null if the index is out of bounds or the array is null. + * @since 3.11 + */ + public static T get(final T[] array, final int index) { + return get(array, index, null); + } + + /** + * Gets the nTh element of an array or a default value if the index is out of bounds. + * + * @param The type of array elements. + * @param array The array to index. + * @param index The index + * @param defaultValue The return value of the given index is out of bounds. + * @return the nTh element of an array or a default value if the index is out of bounds. + * @since 3.11 + */ + public static T get(final T[] array, final int index, final T defaultValue) { + return isArrayIndexValid(array, index) ? array[index] : defaultValue; + } + + /** + * Gets the length of the specified array. + * This method can deal with {@link 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 {@code 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) { + return array != null ? Array.getLength(array) : 0; + } + + /** + * Finds the indices of the given value in the array. + *

+ * This method returns an empty BitSet for a {@code null} input array. + *

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final boolean[] array, final boolean valueToFind) { + return indexesOf(array, valueToFind, 0); + } + + /** + * Finds the indices of the given value in the array starting at the given index. + *

+ * This method returns an empty BitSet for a {@code null} input array. + *

+ *

+ * A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet ({@code -1}). + *

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} + * array input + * @since 3.10 + */ + public static BitSet indexesOf(final boolean[] array, final boolean valueToFind, int startIndex) { + final BitSet bitSet = new BitSet(); + if (array == null) { + return bitSet; + } + while (startIndex < array.length) { + startIndex = indexOf(array, valueToFind, startIndex); + if (startIndex == INDEX_NOT_FOUND) { + break; + } + bitSet.set(startIndex); + ++startIndex; + } + return bitSet; + } + + /** + * Finds the indices of the given value in the array. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final byte[] array, final byte valueToFind) { + return indexesOf(array, valueToFind, 0); + } + + /** + * Finds the indices of the given value in the array starting at the given index. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final byte[] array, final byte valueToFind, int startIndex) { + final BitSet bitSet = new BitSet(); + if (array == null) { + return bitSet; + } + while (startIndex < array.length) { + startIndex = indexOf(array, valueToFind, startIndex); + if (startIndex == INDEX_NOT_FOUND) { + break; + } + bitSet.set(startIndex); + ++startIndex; + } + + return bitSet; + } + + /** + * Finds the indices of the given value in the array. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final char[] array, final char valueToFind) { + return indexesOf(array, valueToFind, 0); + } + + /** + * Finds the indices of the given value in the array starting at the given index. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final char[] array, final char valueToFind, int startIndex) { + final BitSet bitSet = new BitSet(); + if (array == null) { + return bitSet; + } + while (startIndex < array.length) { + startIndex = indexOf(array, valueToFind, startIndex); + if (startIndex == INDEX_NOT_FOUND) { + break; + } + bitSet.set(startIndex); + ++startIndex; + } + return bitSet; + } + + /** + * Finds the indices of the given value in the array. + * + *

This method returns empty BitSet for a {@code null} input array.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final double[] array, final double valueToFind) { + return indexesOf(array, valueToFind, 0); + } + + /** + * Finds the indices of the given value within a given tolerance in the array. + * + *

+ * This method will return all the indices of the value which fall between the region + * defined by valueToFind - tolerance and valueToFind + tolerance, each time between the nearest integers. + *

+ * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @param tolerance tolerance of the search + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final double[] array, final double valueToFind, final double tolerance) { + return indexesOf(array, valueToFind, 0, tolerance); + } + + /** + * Finds the indices of the given value in the array starting at the given index. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return a BitSet of the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final double[] array, final double valueToFind, int startIndex) { + final BitSet bitSet = new BitSet(); + if (array == null) { + return bitSet; + } + while (startIndex < array.length) { + startIndex = indexOf(array, valueToFind, startIndex); + if (startIndex == INDEX_NOT_FOUND) { + break; + } + bitSet.set(startIndex); + ++startIndex; + } + return bitSet; + } + + /** + * Finds the indices of the given value in the array starting at the given index. + * + *

+ * This method will return the indices of the values which fall between the region + * defined by valueToFind - tolerance and valueToFind + tolerance, between the nearest integers. + *

+ * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

+ * + * @param array the array to search 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 a BitSet of the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final double[] array, final double valueToFind, int startIndex, final double tolerance) { + final BitSet bitSet = new BitSet(); + if (array == null) { + return bitSet; + } + while (startIndex < array.length) { + startIndex = indexOf(array, valueToFind, startIndex, tolerance); + if (startIndex == INDEX_NOT_FOUND) { + break; + } + bitSet.set(startIndex); + ++startIndex; + } + return bitSet; + } + + /** + * Finds the indices of the given value in the array. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final float[] array, final float valueToFind) { + return indexesOf(array, valueToFind, 0); + } + + /** + * Finds the indices of the given value in the array starting at the given index. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return empty BitSet.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final float[] array, final float valueToFind, int startIndex) { + final BitSet bitSet = new BitSet(); + if (array == null) { + return bitSet; + } + while (startIndex < array.length) { + startIndex = indexOf(array, valueToFind, startIndex); + if (startIndex == INDEX_NOT_FOUND) { + break; + } + bitSet.set(startIndex); + ++startIndex; + } + return bitSet; + } + + /** + * Finds the indices of the given value in the array. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final int[] array, final int valueToFind) { + return indexesOf(array, valueToFind, 0); + } + + /** + * Finds the indices of the given value in the array starting at the given index. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final int[] array, final int valueToFind, int startIndex) { + final BitSet bitSet = new BitSet(); + if (array == null) { + return bitSet; + } + while (startIndex < array.length) { + startIndex = indexOf(array, valueToFind, startIndex); + + if (startIndex == INDEX_NOT_FOUND) { + break; + } + bitSet.set(startIndex); + ++startIndex; + } + return bitSet; + } + + /** + * Finds the indices of the given value in the array. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final long[] array, final long valueToFind) { + return indexesOf(array, valueToFind, 0); + } + + /** + * Finds the indices of the given value in the array starting at the given index. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final long[] array, final long valueToFind, int startIndex) { + final BitSet bitSet = new BitSet(); + if (array == null) { + return bitSet; + } + while (startIndex < array.length) { + startIndex = indexOf(array, valueToFind, startIndex); + if (startIndex == INDEX_NOT_FOUND) { + break; + } + bitSet.set(startIndex); + ++startIndex; + } + return bitSet; + } + + /** + * Finds the indices of the given object in the array. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + * @param array the array to search for the object, may be {@code null}. + * @param objectToFind the object to find, may be {@code null}. + * @return a BitSet of all the indices of the object within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final Object[] array, final Object objectToFind) { + return indexesOf(array, objectToFind, 0); + } + + /** + * Finds the indices of the given object in the array starting at the given index. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

+ * + * @param array the array to search 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 a BitSet of all the indices of the object within the array starting at the index, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final Object[] array, final Object objectToFind, int startIndex) { + final BitSet bitSet = new BitSet(); + if (array == null) { + return bitSet; + } + while (startIndex < array.length) { + startIndex = indexOf(array, objectToFind, startIndex); + if (startIndex == INDEX_NOT_FOUND) { + break; + } + bitSet.set(startIndex); + ++startIndex; + } + return bitSet; + } + + /** + * Finds the indices of the given value in the array. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final short[] array, final short valueToFind) { + return indexesOf(array, valueToFind, 0); + } + + /** + * Finds the indices of the given value in the array starting at the given index. + * + *

This method returns an empty BitSet for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return an empty BitSet.

+ * + * @param array the array to search for the object, may be {@code null} + * @param valueToFind the value to find + * @param startIndex the index to start searching at + * @return a BitSet of all the indices of the value within the array, + * an empty BitSet if not found or {@code null} array input + * @since 3.10 + */ + public static BitSet indexesOf(final short[] array, final short valueToFind, int startIndex) { + final BitSet bitSet = new BitSet(); + if (array == null) { + return bitSet; + } + while (startIndex < array.length) { + startIndex = indexOf(array, valueToFind, startIndex); + if (startIndex == INDEX_NOT_FOUND) { + break; + } + bitSet.set(startIndex); + ++startIndex; + } + return bitSet; + } + + /** + * 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 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 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, final int startIndex) { + if (isEmpty(array)) { + return INDEX_NOT_FOUND; + } + for (int i = max0(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. + *

+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + *

+ * + * @param array the array to search 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 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, final int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + for (int i = max0(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. + *

+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + *

+ * + * @param array the array to search 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 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, final int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + for (int i = max0(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. + *

+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + *

+ * + * @param array the array to search 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 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 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, final int startIndex) { + if (isEmpty(array)) { + return INDEX_NOT_FOUND; + } + final boolean searchNaN = Double.isNaN(valueToFind); + for (int i = max0(startIndex); i < array.length; i++) { + final double element = array[i]; + if (valueToFind == element || searchNaN && Double.isNaN(element)) { + 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 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, final int startIndex, final double tolerance) { + if (isEmpty(array)) { + return INDEX_NOT_FOUND; + } + final double min = valueToFind - tolerance; + final double max = valueToFind + tolerance; + for (int i = max0(startIndex); i < array.length; i++) { + if (array[i] >= min && array[i] <= max) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + * 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 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 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, final int startIndex) { + if (isEmpty(array)) { + return INDEX_NOT_FOUND; + } + final boolean searchNaN = Float.isNaN(valueToFind); + for (int i = max0(startIndex); i < array.length; i++) { + final float element = array[i]; + if (valueToFind == element || searchNaN && Float.isNaN(element)) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + * 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 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 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, final int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + for (int i = max0(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. + *

+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + *

+ * + * @param array the array to search 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 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, final int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + for (int i = max0(startIndex); i < array.length; i++) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + * 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 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 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; + } + startIndex = max0(startIndex); + if (objectToFind == null) { + for (int i = startIndex; i < array.length; i++) { + if (array[i] == null) { + return i; + } + } + } else { + for (int i = startIndex; i < array.length; i++) { + if (objectToFind.equals(array[i])) { + return i; + } + } + } + return INDEX_NOT_FOUND; + } + + /** + * 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 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 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, final int startIndex) { + if (array == null) { + return INDEX_NOT_FOUND; + } + for (int i = max0(startIndex); i < array.length; i++) { + if (valueToFind == array[i]) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + * Inserts elements into an array at the given index (starting from zero). + * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @param index the position within {@code array} to insert the new values + * @param array the array to insert the values into, may be {@code null} + * @param values the new values to insert, may be {@code null} + * @return The new array or {@code null} if the given array is {@code null}. + * @throws IndexOutOfBoundsException if {@code array} is provided + * and either {@code index < 0} or {@code index > array.length} + * @since 3.6 + */ + public static boolean[] insert(final int index, final boolean[] array, final boolean... values) { + if (array == null) { + return null; + } + if (isEmpty(values)) { + return clone(array); + } + if (index < 0 || index > array.length) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length); + } + final boolean[] result = new boolean[array.length + values.length]; + System.arraycopy(values, 0, result, index, values.length); + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + if (index < array.length) { + System.arraycopy(array, index, result, index + values.length, array.length - index); + } + return result; + } + + /** + * Inserts elements into an array at the given index (starting from zero). + * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @param index the position within {@code array} to insert the new values + * @param array the array to insert the values into, may be {@code null} + * @param values the new values to insert, may be {@code null} + * @return The new array or {@code null} if the given array is {@code null}. + * @throws IndexOutOfBoundsException if {@code array} is provided + * and either {@code index < 0} or {@code index > array.length} + * @since 3.6 + */ + public static byte[] insert(final int index, final byte[] array, final byte... values) { + if (array == null) { + return null; + } + if (isEmpty(values)) { + return clone(array); + } + if (index < 0 || index > array.length) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length); + } + final byte[] result = new byte[array.length + values.length]; + System.arraycopy(values, 0, result, index, values.length); + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + if (index < array.length) { + System.arraycopy(array, index, result, index + values.length, array.length - index); + } + return result; + } + + /** + * Inserts elements into an array at the given index (starting from zero). + * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @param index the position within {@code array} to insert the new values + * @param array the array to insert the values into, may be {@code null} + * @param values the new values to insert, may be {@code null} + * @return The new array or {@code null} if the given array is {@code null}. + * @throws IndexOutOfBoundsException if {@code array} is provided + * and either {@code index < 0} or {@code index > array.length} + * @since 3.6 + */ + public static char[] insert(final int index, final char[] array, final char... values) { + if (array == null) { + return null; + } + if (isEmpty(values)) { + return clone(array); + } + if (index < 0 || index > array.length) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length); + } + final char[] result = new char[array.length + values.length]; + System.arraycopy(values, 0, result, index, values.length); + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + if (index < array.length) { + System.arraycopy(array, index, result, index + values.length, array.length - index); + } + return result; + } + + /** + * Inserts elements into an array at the given index (starting from zero). + * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @param index the position within {@code array} to insert the new values + * @param array the array to insert the values into, may be {@code null} + * @param values the new values to insert, may be {@code null} + * @return The new array or {@code null} if the given array is {@code null}. + * @throws IndexOutOfBoundsException if {@code array} is provided + * and either {@code index < 0} or {@code index > array.length} + * @since 3.6 + */ + public static double[] insert(final int index, final double[] array, final double... values) { + if (array == null) { + return null; + } + if (isEmpty(values)) { + return clone(array); + } + if (index < 0 || index > array.length) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length); + } + final double[] result = new double[array.length + values.length]; + System.arraycopy(values, 0, result, index, values.length); + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + if (index < array.length) { + System.arraycopy(array, index, result, index + values.length, array.length - index); + } + return result; + } + + /** + * Inserts elements into an array at the given index (starting from zero). + * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @param index the position within {@code array} to insert the new values + * @param array the array to insert the values into, may be {@code null} + * @param values the new values to insert, may be {@code null} + * @return The new array or {@code null} if the given array is {@code null}. + * @throws IndexOutOfBoundsException if {@code array} is provided + * and either {@code index < 0} or {@code index > array.length} + * @since 3.6 + */ + public static float[] insert(final int index, final float[] array, final float... values) { + if (array == null) { + return null; + } + if (isEmpty(values)) { + return clone(array); + } + if (index < 0 || index > array.length) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length); + } + final float[] result = new float[array.length + values.length]; + System.arraycopy(values, 0, result, index, values.length); + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + if (index < array.length) { + System.arraycopy(array, index, result, index + values.length, array.length - index); + } + return result; + } + + /** + * Inserts elements into an array at the given index (starting from zero). + * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @param index the position within {@code array} to insert the new values + * @param array the array to insert the values into, may be {@code null} + * @param values the new values to insert, may be {@code null} + * @return The new array or {@code null} if the given array is {@code null}. + * @throws IndexOutOfBoundsException if {@code array} is provided + * and either {@code index < 0} or {@code index > array.length} + * @since 3.6 + */ + public static int[] insert(final int index, final int[] array, final int... values) { + if (array == null) { + return null; + } + if (isEmpty(values)) { + return clone(array); + } + if (index < 0 || index > array.length) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length); + } + final int[] result = new int[array.length + values.length]; + System.arraycopy(values, 0, result, index, values.length); + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + if (index < array.length) { + System.arraycopy(array, index, result, index + values.length, array.length - index); + } + return result; + } + + /** + * Inserts elements into an array at the given index (starting from zero). + * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @param index the position within {@code array} to insert the new values + * @param array the array to insert the values into, may be {@code null} + * @param values the new values to insert, may be {@code null} + * @return The new array or {@code null} if the given array is {@code null}. + * @throws IndexOutOfBoundsException if {@code array} is provided + * and either {@code index < 0} or {@code index > array.length} + * @since 3.6 + */ + public static long[] insert(final int index, final long[] array, final long... values) { + if (array == null) { + return null; + } + if (isEmpty(values)) { + return clone(array); + } + if (index < 0 || index > array.length) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length); + } + final long[] result = new long[array.length + values.length]; + System.arraycopy(values, 0, result, index, values.length); + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + if (index < array.length) { + System.arraycopy(array, index, result, index + values.length, array.length - index); + } + return result; + } + + /** + * Inserts elements into an array at the given index (starting from zero). + * + *

When an array is returned, it is always a new array.

+ * + *
+     * ArrayUtils.insert(index, null, null)      = null
+     * ArrayUtils.insert(index, array, null)     = cloned copy of 'array'
+     * ArrayUtils.insert(index, null, values)    = null
+     * 
+ * + * @param index the position within {@code array} to insert the new values + * @param array the array to insert the values into, may be {@code null} + * @param values the new values to insert, may be {@code null} + * @return The new array or {@code null} if the given array is {@code null}. + * @throws IndexOutOfBoundsException if {@code array} is provided + * and either {@code index < 0} or {@code index > array.length} + * @since 3.6 + */ + public static short[] insert(final int index, final short[] array, final short... values) { + if (array == null) { + return null; + } + if (isEmpty(values)) { + return clone(array); + } + if (index < 0 || index > array.length) { + throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length); + } + final short[] result = new short[array.length + values.length]; + System.arraycopy(values, 0, result, index, values.length); + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + if (index < array.length) { + System.arraycopy(array, index, result, index + values.length, array.length - index); + } + return result; + } + + /** + * Checks if an array is empty or {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is empty or {@code null} + */ + private static boolean isArrayEmpty(final Object array) { + return getLength(array) == 0; + } + + /** + * Tests whether a given array can safely be accessed at the given index. + * + *
+     * ArrayUtils.isArrayIndexValid(null, 0)       = false
+     * ArrayUtils.isArrayIndexValid([], 0)         = false
+     * ArrayUtils.isArrayIndexValid(["a"], 0)      = true
+     * 
+ * + * @param the component type of the array + * @param array the array to inspect, may be {@code null}. + * @param index the index of the array to be inspected + * @return Whether the given index is safely-accessible in the given array + * @since 3.8 + */ + public static boolean isArrayIndexValid(final T[] array, final int index) { + return index >= 0 && getLength(array) > index; + } + + /** + * Tests whether 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 isArrayEmpty(array); + } + + /** + * Tests whether 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 isArrayEmpty(array); + } + + /** + * Tests whether 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 isArrayEmpty(array); + } + + /** + * Tests whether 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 isArrayEmpty(array); + } + + /** + * Tests whether 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 isArrayEmpty(array); + } + + /** + * Tests whether 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 isArrayEmpty(array); + } + + /** + * Tests whether 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 isArrayEmpty(array); + } + + /** + * Tests whether 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 isArrayEmpty(array); + } + + /** + * Tests whether 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 isArrayEmpty(array); + } + + /** + * Tests whether an array of primitive booleans is not empty and not {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is not empty and not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final boolean[] array) { + return !isEmpty(array); + } + + /** + * Tests whether an array of primitive bytes is not empty and not {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is not empty and not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final byte[] array) { + return !isEmpty(array); + } + + /** + * Tests whether an array of primitive chars is not empty and not {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is not empty and not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final char[] array) { + return !isEmpty(array); + } + + /** + * Tests whether an array of primitive doubles is not empty and not {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is not empty and not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final double[] array) { + return !isEmpty(array); + } + + /** + * Tests whether an array of primitive floats is not empty and not {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is not empty and not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final float[] array) { + return !isEmpty(array); + } + + /** + * Tests whether an array of primitive ints is not empty and not {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is not empty and not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final int[] array) { + return !isEmpty(array); + } + + /** + * Tests whether an array of primitive longs is not empty and not {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is not empty and not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final long[] array) { + return !isEmpty(array); + } + + /** + * Tests whether an array of primitive shorts is not empty and not {@code null}. + * + * @param array the array to test + * @return {@code true} if the array is not empty and not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final short[] array) { + return !isEmpty(array); + } + + /** + * Tests whether an array of Objects is not empty and 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 and not {@code null} + * @since 2.5 + */ + public static boolean isNotEmpty(final T[] array) { + return !isEmpty(array); + } + + /** + * Tests 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) { + return getLength(array1) == getLength(array2); + } + + /** + * Tests 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) { + return getLength(array1) == getLength(array2); + } + + /** + * Tests 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) { + return getLength(array1) == getLength(array2); + } + + /** + * Tests 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) { + return getLength(array1) == getLength(array2); + } + + /** + * Tests 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) { + return getLength(array1) == getLength(array2); + } + + /** + * Tests 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) { + return getLength(array1) == getLength(array2); + } + + /** + * Tests 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) { + return getLength(array1) == getLength(array2); + } + + /** + * Tests 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 + * @since 3.11 + */ + public static boolean isSameLength(final Object array1, final Object array2) { + return getLength(array1) == getLength(array2); + } + + /** + * Tests 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) { + return getLength(array1) == getLength(array2); + } + + /** + * Tests 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) { + return getLength(array1) == getLength(array2); + } + + /** + * Tests whether two arrays are the same type taking into account + * multidimensional 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()); + } + + /** + * Tests 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 (getLength(array) < 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; + } + + /** + * Tests 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 (getLength(array) < 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; + } + + /** + * Tests 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 {@link Comparable} + * @return whether the array is sorted + * @since 3.4 + */ + public static > boolean isSorted(final T[] array) { + return isSorted(array, Comparable::compareTo); + } + + /** + * Tests whether the provided array is sorted according to the provided {@link Comparator}. + * + * @param array the array to check + * @param comparator the {@link Comparator} to compare over + * @param the datatype of the array + * @return whether the array is sorted + * @throws NullPointerException if {@code comparator} is {@code null} + * @since 3.4 + */ + public static boolean isSorted(final T[] array, final Comparator comparator) { + Objects.requireNonNull(comparator, "comparator"); + if (getLength(array) < 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; + } + + /** + * 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 traverse backwards 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 traverse 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 (isEmpty(array) || startIndex < 0) { + return INDEX_NOT_FOUND; + } + 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 within the array. + *

+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + *

+ * + * @param array the array to traverse backwards 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 traverse 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 || startIndex < 0) { + return INDEX_NOT_FOUND; + } + 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 within the array. + *

+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + *

+ * + * @param array the array to traverse backwards 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 traverse 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 || startIndex < 0) { + return INDEX_NOT_FOUND; + } + 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 within the array. + *

+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + *

+ * + * @param array the array to traverse backwards 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 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 traverse 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 (isEmpty(array) || startIndex < 0) { + return INDEX_NOT_FOUND; + } + 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 traverse 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 (isEmpty(array) || startIndex < 0) { + return INDEX_NOT_FOUND; + } + 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; + } + + /** + * 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 traverse backwards 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 traverse 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 (isEmpty(array) || startIndex < 0) { + return INDEX_NOT_FOUND; + } + 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 within the array. + *

+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + *

+ * + * @param array the array to traverse backwards 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 traverse 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 || startIndex < 0) { + return INDEX_NOT_FOUND; + } + 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 within the array. + *

+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + *

+ * + * @param array the array to traverse backwards 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 traverse 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 || startIndex < 0) { + return INDEX_NOT_FOUND; + } + 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 object within the array. + *

+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array. + *

+ * + * @param array the array to traverse backwards 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 traverse 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 || startIndex < 0) { + return INDEX_NOT_FOUND; + } + 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; + } + + /** + * 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 traverse backwards 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 traverse 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 || startIndex < 0) { + return INDEX_NOT_FOUND; + } + 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; + } + + private static int max0(final int other) { + return Math.max(0, other); + } + + /** + * Delegates to {@link Array#newInstance(Class,int)} using generics. + * + * @param The array type. + * @param componentType The array class. + * @param length the array length + * @return The new array. + * @throws NullPointerException if the specified {@code componentType} parameter is null. + * @since 3.13.0 + */ + @SuppressWarnings("unchecked") // OK, because array and values are of type T + public static T[] newInstance(final Class componentType, final int length) { + return (T[]) Array.newInstance(componentType, length); + } + + /** + * Defensive programming technique to change a {@code null} + * reference to an empty one. + *

+ * This method returns a default 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 The array type. + * @param array the array to check for {@code null} or empty + * @param defaultArray A default array, usually empty. + * @return the same array, or defaultArray if {@code null} or empty input. + * @since 3.15.0 + */ + public static T[] nullTo(final T[] array, final T[] defaultArray) { + return isEmpty(array) ? defaultArray : 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) { + return isEmpty(array) ? EMPTY_BOOLEAN_ARRAY : 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) { + return nullTo(array, EMPTY_BOOLEAN_OBJECT_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) { + return isEmpty(array) ? EMPTY_BYTE_ARRAY : 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) { + return nullTo(array, EMPTY_BYTE_OBJECT_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) { + return isEmpty(array) ? EMPTY_CHAR_ARRAY : 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) { + return nullTo(array, EMPTY_CHARACTER_OBJECT_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) { + return nullTo(array, EMPTY_CLASS_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) { + return isEmpty(array) ? EMPTY_DOUBLE_ARRAY : 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) { + return nullTo(array, EMPTY_DOUBLE_OBJECT_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) { + return isEmpty(array) ? EMPTY_FLOAT_ARRAY : 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) { + return nullTo(array, EMPTY_FLOAT_OBJECT_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) { + return isEmpty(array) ? EMPTY_INT_ARRAY : 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) { + return nullTo(array, EMPTY_INTEGER_OBJECT_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) { + return isEmpty(array) ? EMPTY_LONG_ARRAY : 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) { + return nullTo(array, EMPTY_LONG_OBJECT_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 Object[] nullToEmpty(final Object[] array) { + return nullTo(array, EMPTY_OBJECT_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) { + return isEmpty(array) ? EMPTY_SHORT_ARRAY : 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) { + return nullTo(array, EMPTY_SHORT_OBJECT_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) { + return nullTo(array, EMPTY_STRING_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. + *

+ * + * @param array the array to check for {@code null} or empty + * @param type the class representation of the desired array + * @param the class type + * @return the same array, {@code public static} empty array if {@code null} + * @throws IllegalArgumentException if the type argument is null + * @since 3.5 + */ + public static T[] nullToEmpty(final T[] array, final Class type) { + if (type == null) { + throw new IllegalArgumentException("The type must not be null"); + } + if (array == null) { + return type.cast(Array.newInstance(type.getComponentType(), 0)); + } + return array; + } + + /** + * Gets the {@link ThreadLocalRandom} for {@code shuffle} methods that don't take a {@link Random} argument. + * + * @return the current ThreadLocalRandom. + */ + private static ThreadLocalRandom random() { + return ThreadLocalRandom.current(); + } + + /** + * 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 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 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 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 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 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 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 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 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 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 occurrences of the specified element from the specified boolean array. + *

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contain such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param array the input array, will not be modified, and may be {@code null}. + * @param element the element to remove. + * @return A new array containing the existing elements except the occurrences of the specified element. + * @since 3.10 + */ + public static boolean[] removeAllOccurrences(final boolean[] array, final boolean element) { + return (boolean[]) removeAt(array, indexesOf(array, element)); + } + + /** + * Removes the occurrences of the specified element from the specified byte array. + *

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contain such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param array the input array, will not be modified, and may be {@code null}. + * @param element the element to remove. + * @return A new array containing the existing elements except the occurrences of the specified element. + * @since 3.10 + */ + public static byte[] removeAllOccurrences(final byte[] array, final byte element) { + return (byte[]) removeAt(array, indexesOf(array, element)); + } + + /** + * Removes the occurrences of the specified element from the specified char array. + *

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contain such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param array the input array, will not be modified, and may be {@code null}. + * @param element the element to remove. + * @return A new array containing the existing elements except the occurrences of the specified element. + * @since 3.10 + */ + public static char[] removeAllOccurrences(final char[] array, final char element) { + return (char[]) removeAt(array, indexesOf(array, element)); + } + + /** + * Removes the occurrences of the specified element from the specified double array. + *

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contain such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param array the input array, will not be modified, and may be {@code null}. + * @param element the element to remove. + * @return A new array containing the existing elements except the occurrences of the specified element. + * @since 3.10 + */ + public static double[] removeAllOccurrences(final double[] array, final double element) { + return (double[]) removeAt(array, indexesOf(array, element)); + } + + /** + * Removes the occurrences of the specified element from the specified float array. + *

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contain such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param array the input array, will not be modified, and may be {@code null}. + * @param element the element to remove. + * @return A new array containing the existing elements except the occurrences of the specified element. + * @since 3.10 + */ + public static float[] removeAllOccurrences(final float[] array, final float element) { + return (float[]) removeAt(array, indexesOf(array, element)); + } + + /** + * Removes the occurrences of the specified element from the specified int array. + *

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contain such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param array the input array, will not be modified, and may be {@code null}. + * @param element the element to remove. + * @return A new array containing the existing elements except the occurrences of the specified element. + * @since 3.10 + */ + public static int[] removeAllOccurrences(final int[] array, final int element) { + return (int[]) removeAt(array, indexesOf(array, element)); + } + + /** + * Removes the occurrences of the specified element from the specified long array. + *

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contain such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param array the input array, will not be modified, and may be {@code null}. + * @param element the element to remove. + * @return A new array containing the existing elements except the occurrences of the specified element. + * @since 3.10 + */ + public static long[] removeAllOccurrences(final long[] array, final long element) { + return (long[]) removeAt(array, indexesOf(array, element)); + } + + /** + * Removes the occurrences of the specified element from the specified short array. + *

+ * All subsequent elements are shifted to the left (subtracts one from their indices). + * If the array doesn't contain such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param array the input array, will not be modified, and may be {@code null}. + * @param element the element to remove. + * @return A new array containing the existing elements except the occurrences of the specified element. + * @since 3.10 + */ + public static short[] removeAllOccurrences(final short[] array, final short element) { + return (short[]) removeAt(array, indexesOf(array, element)); + } + + /** + * Removes the occurrences 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 contain such an element, no elements are removed from the array. + * {@code null} will be returned if the input array is {@code null}. + *

+ * + * @param the type of object in the array, may be {@code null}. + * @param array the input array, will not be modified, and may be {@code null}. + * @param element the element to remove, may be {@code null}. + * @return A new array containing the existing elements except the occurrences of the specified element. + * @since 3.10 + */ + @SuppressWarnings("unchecked") + public static T[] removeAllOccurrences(final T[] array, final T element) { + return (T[]) removeAt(array, indexesOf(array, element)); + } + + /** + * Removes multiple array elements specified by indices. + * + * @param array the input array, will not be modified, and may be {@code null}. + * @param indices to remove. + * @return new array of same type minus elements specified by the set bits in {@code indices}. + */ + // package protected for access by unit tests + static Object removeAt(final Object array, final BitSet indices) { + if (array == null) { + return null; + } + final int srcLength = getLength(array); + // No need to check maxIndex here, because method only currently called from removeElements() + // which guarantee to generate only 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; + } + + /** + * 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 contain + * 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 input array, 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); + return index == INDEX_NOT_FOUND ? clone(array) : remove(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 contain + * 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 input array, 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); + return index == INDEX_NOT_FOUND ? clone(array) : remove(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 contain + * 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 input array, 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); + return index == INDEX_NOT_FOUND ? clone(array) : remove(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 contain + * 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 input array, 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); + return index == INDEX_NOT_FOUND ? clone(array) : remove(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 contain + * 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 input array, 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); + return index == INDEX_NOT_FOUND ? clone(array) : remove(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 contain + * 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 input array, 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); + return index == INDEX_NOT_FOUND ? clone(array) : remove(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 contain + * 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 input array, 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); + return index == INDEX_NOT_FOUND ? clone(array) : remove(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 contain + * 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 input array, 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); + return index == INDEX_NOT_FOUND ? clone(array) : remove(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 contain + * 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 input array, may be {@code null}. + * @param element the element to be removed, may be {@code null}. + * @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); + return index == INDEX_NOT_FOUND ? clone(array) : remove(array, index); + } + + /** + * 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 = Math.max(startIndexInclusive, 0); + 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. + *

+ * 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) { + 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 byte[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = Math.max(startIndexInclusive, 0); + 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. + *

+ * 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) { + 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 char[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = Math.max(startIndexInclusive, 0); + 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. + *

+ * 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) { + 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 double[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = Math.max(startIndexInclusive, 0); + 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. + *

+ * 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) { + 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 float[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = Math.max(startIndexInclusive, 0); + 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. + *

+ * 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) { + 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 int[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = Math.max(startIndexInclusive, 0); + 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. + *

+ * 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) { + 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 long[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = Math.max(startIndexInclusive, 0); + 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. + *

+ * 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) { + 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. Under value (<0) is promoted to 0, over value (>array.length) results in no + * change. + * @param endIndexExclusive + * elements up to endIndex-1 are reversed in the array. Under value (< start index) results in no + * change. Over value (>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 = Math.max(startIndexInclusive, 0); + 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. + *

+ * 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) { + 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 short[] array, final int startIndexInclusive, final int endIndexExclusive) { + if (array == null) { + return; + } + int i = Math.max(startIndexInclusive, 0); + 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++; + } + } + + /** + * Sets all elements of the specified array, using the provided generator supplier to compute each element. + *

+ * If the generator supplier throws an exception, it is relayed to the caller and the array is left in an indeterminate + * state. + *

+ * + * @param type of elements of the array, may be {@code null}. + * @param array array to be initialized, may be {@code null}. + * @param generator a function accepting an index and producing the desired value for that position. + * @return the input array + * @since 3.13.0 + */ + public static T[] setAll(final T[] array, final IntFunction generator) { + if (array != null && generator != null) { + Arrays.setAll(array, generator); + } + return array; + } + + /** + * Sets all elements of the specified array, using the provided generator supplier to compute each element. + *

+ * If the generator supplier throws an exception, it is relayed to the caller and the array is left in an indeterminate + * state. + *

+ * + * @param type of elements of the array, may be {@code null}. + * @param array array to be initialized, may be {@code null}. + * @param generator a function accepting an index and producing the desired value for that position. + * @return the input array + * @since 3.13.0 + */ + public static T[] setAll(final T[] array, final Supplier generator) { + if (array != null && generator != null) { + for (int i = 0; i < array.length; i++) { + array[i] = generator.get(); + } + } + return array; + } + + /** + * Shifts the order of the given boolean array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array the array to shift, may be {@code null}. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final boolean[] array, final int offset) { + if (array != null) { + shift(array, 0, array.length, offset); + } + } + + /** + * Shifts the order of a series of elements in the given boolean array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, 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 shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final boolean[] array, int startIndexInclusive, int endIndexExclusive, int offset) { + if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) { + return; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + int n = endIndexExclusive - startIndexInclusive; + if (n <= 1) { + return; + } + offset %= n; + if (offset < 0) { + offset += n; + } + // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity + // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/ + while (n > 1 && offset > 0) { + final int nOffset = n - offset; + if (offset > nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + n - nOffset, nOffset); + n = offset; + offset -= nOffset; + } else if (offset < nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + startIndexInclusive += offset; + n = nOffset; + } else { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + break; + } + } + } + + /** + * Shifts the order of the given byte array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array the array to shift, may be {@code null}. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final byte[] array, final int offset) { + if (array != null) { + shift(array, 0, array.length, offset); + } + } + + /** + * Shifts the order of a series of elements in the given byte array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, 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 shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final byte[] array, int startIndexInclusive, int endIndexExclusive, int offset) { + if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) { + return; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + int n = endIndexExclusive - startIndexInclusive; + if (n <= 1) { + return; + } + offset %= n; + if (offset < 0) { + offset += n; + } + // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity + // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/ + while (n > 1 && offset > 0) { + final int nOffset = n - offset; + if (offset > nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + n - nOffset, nOffset); + n = offset; + offset -= nOffset; + } else if (offset < nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + startIndexInclusive += offset; + n = nOffset; + } else { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + break; + } + } + } + + /** + * Shifts the order of the given char array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array the array to shift, may be {@code null}. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final char[] array, final int offset) { + if (array != null) { + shift(array, 0, array.length, offset); + } + } + + /** + * Shifts the order of a series of elements in the given char array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, 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 shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final char[] array, int startIndexInclusive, int endIndexExclusive, int offset) { + if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) { + return; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + int n = endIndexExclusive - startIndexInclusive; + if (n <= 1) { + return; + } + offset %= n; + if (offset < 0) { + offset += n; + } + // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity + // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/ + while (n > 1 && offset > 0) { + final int nOffset = n - offset; + if (offset > nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + n - nOffset, nOffset); + n = offset; + offset -= nOffset; + } else if (offset < nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + startIndexInclusive += offset; + n = nOffset; + } else { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + break; + } + } + } + + /** + * Shifts the order of the given double array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array the array to shift, may be {@code null}. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final double[] array, final int offset) { + if (array != null) { + shift(array, 0, array.length, offset); + } + } + + /** + * Shifts the order of a series of elements in the given double array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, 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 shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final double[] array, int startIndexInclusive, int endIndexExclusive, int offset) { + if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) { + return; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + int n = endIndexExclusive - startIndexInclusive; + if (n <= 1) { + return; + } + offset %= n; + if (offset < 0) { + offset += n; + } + // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity + // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/ + while (n > 1 && offset > 0) { + final int nOffset = n - offset; + if (offset > nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + n - nOffset, nOffset); + n = offset; + offset -= nOffset; + } else if (offset < nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + startIndexInclusive += offset; + n = nOffset; + } else { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + break; + } + } + } + + /** + * Shifts the order of the given float array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array the array to shift, may be {@code null}. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final float[] array, final int offset) { + if (array != null) { + shift(array, 0, array.length, offset); + } + } + + /** + * Shifts the order of a series of elements in the given float array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, 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 shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final float[] array, int startIndexInclusive, int endIndexExclusive, int offset) { + if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) { + return; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + int n = endIndexExclusive - startIndexInclusive; + if (n <= 1) { + return; + } + offset %= n; + if (offset < 0) { + offset += n; + } + // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity + // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/ + while (n > 1 && offset > 0) { + final int nOffset = n - offset; + if (offset > nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + n - nOffset, nOffset); + n = offset; + offset -= nOffset; + } else if (offset < nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + startIndexInclusive += offset; + n = nOffset; + } else { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + break; + } + } + } + + /** + * Shifts the order of the given int array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array the array to shift, may be {@code null}. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final int[] array, final int offset) { + if (array != null) { + shift(array, 0, array.length, offset); + } + } + + /** + * Shifts the order of a series of elements in the given int array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, 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 shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final int[] array, int startIndexInclusive, int endIndexExclusive, int offset) { + if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) { + return; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + int n = endIndexExclusive - startIndexInclusive; + if (n <= 1) { + return; + } + offset %= n; + if (offset < 0) { + offset += n; + } + // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity + // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/ + while (n > 1 && offset > 0) { + final int nOffset = n - offset; + if (offset > nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + n - nOffset, nOffset); + n = offset; + offset -= nOffset; + } else if (offset < nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + startIndexInclusive += offset; + n = nOffset; + } else { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + break; + } + } + } + + /** + * Shifts the order of the given long array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array the array to shift, may be {@code null}. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final long[] array, final int offset) { + if (array != null) { + shift(array, 0, array.length, offset); + } + } + + /** + * Shifts the order of a series of elements in the given long array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, 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 shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final long[] array, int startIndexInclusive, int endIndexExclusive, int offset) { + if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) { + return; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + int n = endIndexExclusive - startIndexInclusive; + if (n <= 1) { + return; + } + offset %= n; + if (offset < 0) { + offset += n; + } + // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity + // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/ + while (n > 1 && offset > 0) { + final int nOffset = n - offset; + if (offset > nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + n - nOffset, nOffset); + n = offset; + offset -= nOffset; + } else if (offset < nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + startIndexInclusive += offset; + n = nOffset; + } else { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + break; + } + } + } + + /** + * Shifts the order of the given array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array the array to shift, may be {@code null}. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final Object[] array, final int offset) { + if (array != null) { + shift(array, 0, array.length, offset); + } + } + + /** + * Shifts the order of a series of elements in the given array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, 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 shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final Object[] array, int startIndexInclusive, int endIndexExclusive, int offset) { + if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) { + return; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + int n = endIndexExclusive - startIndexInclusive; + if (n <= 1) { + return; + } + offset %= n; + if (offset < 0) { + offset += n; + } + // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity + // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/ + while (n > 1 && offset > 0) { + final int nOffset = n - offset; + if (offset > nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + n - nOffset, nOffset); + n = offset; + offset -= nOffset; + } else if (offset < nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + startIndexInclusive += offset; + n = nOffset; + } else { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + break; + } + } + } + + /** + * Shifts the order of the given short array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array the array to shift, may be {@code null}. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final short[] array, final int offset) { + if (array != null) { + shift(array, 0, array.length, offset); + } + } + + /** + * Shifts the order of a series of elements in the given short array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for {@code null} or empty input arrays.

+ * + * @param array + * the array to shift, 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 shifted in the array. Undervalue (< start index) results in no + * change. Overvalue (>array.length) is demoted to array length. + * @param offset + * The number of positions to rotate the elements. If the offset is larger than the number of elements to + * rotate, than the effective offset is modulo the number of elements to rotate. + * @since 3.5 + */ + public static void shift(final short[] array, int startIndexInclusive, int endIndexExclusive, int offset) { + if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) { + return; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + int n = endIndexExclusive - startIndexInclusive; + if (n <= 1) { + return; + } + offset %= n; + if (offset < 0) { + offset += n; + } + // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity + // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/ + while (n > 1 && offset > 0) { + final int nOffset = n - offset; + if (offset > nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + n - nOffset, nOffset); + n = offset; + offset -= nOffset; + } else if (offset < nOffset) { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + startIndexInclusive += offset; + n = nOffset; + } else { + swap(array, startIndexInclusive, startIndexInclusive + nOffset, offset); + break; + } + } + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + *

+ * This method uses the current {@link ThreadLocalRandom} as its random number generator. + *

+ *

+ * Instances of {@link ThreadLocalRandom} are not cryptographically secure. For security-sensitive applications, consider using a {@code shuffle} method + * with a {@link SecureRandom} argument. + *

+ * + * @param array the array to shuffle. + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final boolean[] array) { + shuffle(array, random()); + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + * + * @param array the array to shuffle + * @param random the source of randomness used to permute the elements + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final boolean[] array, final Random random) { + for (int i = array.length; i > 1; i--) { + swap(array, i - 1, random.nextInt(i), 1); + } + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + *

+ * This method uses the current {@link ThreadLocalRandom} as its random number generator. + *

+ *

+ * Instances of {@link ThreadLocalRandom} are not cryptographically secure. For security-sensitive applications, consider using a {@code shuffle} method + * with a {@link SecureRandom} argument. + *

+ * + * @param array the array to shuffle. + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final byte[] array) { + shuffle(array, random()); + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + * + * @param array the array to shuffle + * @param random the source of randomness used to permute the elements + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final byte[] array, final Random random) { + for (int i = array.length; i > 1; i--) { + swap(array, i - 1, random.nextInt(i), 1); + } + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + *

+ * This method uses the current {@link ThreadLocalRandom} as its random number generator. + *

+ *

+ * Instances of {@link ThreadLocalRandom} are not cryptographically secure. For security-sensitive applications, consider using a {@code shuffle} method + * with a {@link SecureRandom} argument. + *

+ * + * @param array the array to shuffle. + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final char[] array) { + shuffle(array, random()); + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + * + * @param array the array to shuffle + * @param random the source of randomness used to permute the elements + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final char[] array, final Random random) { + for (int i = array.length; i > 1; i--) { + swap(array, i - 1, random.nextInt(i), 1); + } + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + *

+ * This method uses the current {@link ThreadLocalRandom} as its random number generator. + *

+ *

+ * Instances of {@link ThreadLocalRandom} are not cryptographically secure. For security-sensitive applications, consider using a {@code shuffle} method + * with a {@link SecureRandom} argument. + *

+ * + * @param array the array to shuffle. + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final double[] array) { + shuffle(array, random()); + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + * + * @param array the array to shuffle + * @param random the source of randomness used to permute the elements + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final double[] array, final Random random) { + for (int i = array.length; i > 1; i--) { + swap(array, i - 1, random.nextInt(i), 1); + } + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + *

+ * This method uses the current {@link ThreadLocalRandom} as its random number generator. + *

+ *

+ * Instances of {@link ThreadLocalRandom} are not cryptographically secure. For security-sensitive applications, consider using a {@code shuffle} method + * with a {@link SecureRandom} argument. + *

+ * + * @param array the array to shuffle. + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final float[] array) { + shuffle(array, random()); + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + * + * @param array the array to shuffle + * @param random the source of randomness used to permute the elements + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final float[] array, final Random random) { + for (int i = array.length; i > 1; i--) { + swap(array, i - 1, random.nextInt(i), 1); + } + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + *

+ * This method uses the current {@link ThreadLocalRandom} as its random number generator. + *

+ *

+ * Instances of {@link ThreadLocalRandom} are not cryptographically secure. For security-sensitive applications, consider using a {@code shuffle} method + * with a {@link SecureRandom} argument. + *

+ * + * @param array the array to shuffle. + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final int[] array) { + shuffle(array, random()); + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + * + * @param array the array to shuffle + * @param random the source of randomness used to permute the elements + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final int[] array, final Random random) { + for (int i = array.length; i > 1; i--) { + swap(array, i - 1, random.nextInt(i), 1); + } + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + *

+ * This method uses the current {@link ThreadLocalRandom} as its random number generator. + *

+ *

+ * Instances of {@link ThreadLocalRandom} are not cryptographically secure. For security-sensitive applications, consider using a {@code shuffle} method + * with a {@link SecureRandom} argument. + *

+ * + * @param array the array to shuffle. + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final long[] array) { + shuffle(array, random()); + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + * + * @param array the array to shuffle + * @param random the source of randomness used to permute the elements + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final long[] array, final Random random) { + for (int i = array.length; i > 1; i--) { + swap(array, i - 1, random.nextInt(i), 1); + } + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + *

+ * This method uses the current {@link ThreadLocalRandom} as its random number generator. + *

+ *

+ * Instances of {@link ThreadLocalRandom} are not cryptographically secure. For security-sensitive applications, consider using a {@code shuffle} method + * with a {@link SecureRandom} argument. + *

+ * + * @param array the array to shuffle. + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final Object[] array) { + shuffle(array, random()); + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + * + * @param array the array to shuffle + * @param random the source of randomness used to permute the elements + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final Object[] array, final Random random) { + for (int i = array.length; i > 1; i--) { + swap(array, i - 1, random.nextInt(i), 1); + } + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + *

+ * This method uses the current {@link ThreadLocalRandom} as its random number generator. + *

+ *

+ * Instances of {@link ThreadLocalRandom} are not cryptographically secure. For security-sensitive applications, consider using a {@code shuffle} method + * with a {@link SecureRandom} argument. + *

+ * + * @param array the array to shuffle. + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final short[] array) { + shuffle(array, random()); + } + + /** + * Shuffles randomly the elements of the specified array using the Fisher-Yates shuffle + * algorithm. + * + * @param array the array to shuffle + * @param random the source of randomness used to permute the elements + * @see Fisher-Yates shuffle algorithm + * @since 3.6 + */ + public static void shuffle(final short[] array, final Random random) { + for (int i = array.length; i > 1; i--) { + swap(array, i - 1, random.nextInt(i), 1); + } + } + + /** + * Tests whether the given data array starts with an expected array, for example, signature bytes. + *

+ * If both arrays are null, the method returns true. The method return false when one array is null and the other not. + *

+ * + * @param data The data to search, maybe larger than the expected data. + * @param expected The expected data to find. + * @return whether a match was found. + * @since 3.18.0 + */ + public static boolean startsWith(final byte[] data, final byte[] expected) { + if (data == expected) { + return true; + } + if (data == null || expected == null) { + return false; + } + final int dataLen = data.length; + if (expected.length > dataLen) { + return false; + } + if (expected.length == dataLen) { + // delegate to Arrays.equals() which has optimizations on Java > 8 + return Arrays.equals(data, expected); + } + // Once we are on Java 9+ we can delegate to Arrays here as well (or not). + for (int i = 0; i < expected.length; i++) { + if (data[i] != expected[i]) { + return false; + } + } + return true; + } + + /** + * 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; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_BOOLEAN_ARRAY; + } + return arraycopy(array, startIndexInclusive, 0, newSize, boolean[]::new); + } + + /** + * 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; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_BYTE_ARRAY; + } + return arraycopy(array, startIndexInclusive, 0, newSize, byte[]::new); + } + + /** + * 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; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_CHAR_ARRAY; + } + return arraycopy(array, startIndexInclusive, 0, newSize, char[]::new); + } + + /** + * 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; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_DOUBLE_ARRAY; + } + return arraycopy(array, startIndexInclusive, 0, newSize, double[]::new); + } + + /** + * 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; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_FLOAT_ARRAY; + } + return arraycopy(array, startIndexInclusive, 0, newSize, float[]::new); + } + + /** + * 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; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_INT_ARRAY; + } + return arraycopy(array, startIndexInclusive, 0, newSize, int[]::new); + } + + /** + * 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; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_LONG_ARRAY; + } + return arraycopy(array, startIndexInclusive, 0, newSize, long[]::new); + } + + /** + * 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; + } + startIndexInclusive = max0(startIndexInclusive); + endIndexExclusive = Math.min(endIndexExclusive, array.length); + final int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_SHORT_ARRAY; + } + return arraycopy(array, startIndexInclusive, 0, newSize, short[]::new); + } + + /** + * Swaps two elements in the given boolean array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for a {@code null} or empty input array or for overflow indices. + * Negative indices are promoted to 0(zero).

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @param array the array to swap, may be {@code null}. + * @param offset1 the index of the first element to swap + * @param offset2 the index of the second element to swap + * @since 3.5 + */ + public static void swap(final boolean[] array, final int offset1, final int offset2) { + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given boolean array. + * + *

This method does nothing for a {@code null} or empty input array or + * for overflow indices. Negative indices are promoted to 0(zero). If any + * of the sub-arrays to swap falls outside of the given array, then the + * swap is stopped at the end of the array and as many as possible elements + * are swapped.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([true, false, true, false], 0, 2, 1) -> [true, false, true, false]
  • + *
  • ArrayUtils.swap([true, false, true, false], 0, 0, 1) -> [true, false, true, false]
  • + *
  • ArrayUtils.swap([true, false, true, false], 0, 2, 2) -> [true, false, true, false]
  • + *
  • ArrayUtils.swap([true, false, true, false], -3, 2, 2) -> [true, false, true, false]
  • + *
  • ArrayUtils.swap([true, false, true, false], 0, 3, 3) -> [false, false, true, true]
  • + *
+ * + * @param array the array to swap, may be {@code null}. + * @param offset1 the index of the first element in the series to swap + * @param offset2 the index of the second element in the series to swap + * @param len the number of elements to swap starting with the given indices + * @since 3.5 + */ + public static void swap(final boolean[] array, int offset1, int offset2, int len) { + if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) { + return; + } + offset1 = max0(offset1); + offset2 = max0(offset2); + len = Math.min(Math.min(len, array.length - offset1), array.length - offset2); + for (int i = 0; i < len; i++, offset1++, offset2++) { + final boolean aux = array[offset1]; + array[offset1] = array[offset2]; + array[offset2] = aux; + } + } + + /** + * Swaps two elements in the given byte array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for a {@code null} or empty input array or for overflow indices. + * Negative indices are promoted to 0(zero).

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @param array the array to swap, may be {@code null}. + * @param offset1 the index of the first element to swap + * @param offset2 the index of the second element to swap + * @since 3.5 + */ + public static void swap(final byte[] array, final int offset1, final int offset2) { + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given byte array. + * + *

This method does nothing for a {@code null} or empty input array or + * for overflow indices. Negative indices are promoted to 0(zero). If any + * of the sub-arrays to swap falls outside of the given array, then the + * swap is stopped at the end of the array and as many as possible elements + * are swapped.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @param array the array to swap, may be {@code null}. + * @param offset1 the index of the first element in the series to swap + * @param offset2 the index of the second element in the series to swap + * @param len the number of elements to swap starting with the given indices + * @since 3.5 + */ + public static void swap(final byte[] array, int offset1, int offset2, int len) { + if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) { + return; + } + offset1 = max0(offset1); + offset2 = max0(offset2); + len = Math.min(Math.min(len, array.length - offset1), array.length - offset2); + for (int i = 0; i < len; i++, offset1++, offset2++) { + final byte aux = array[offset1]; + array[offset1] = array[offset2]; + array[offset2] = aux; + } + } + + /** + * Swaps two elements in the given char array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for a {@code null} or empty input array or for overflow indices. + * Negative indices are promoted to 0(zero).

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @param array the array to swap, may be {@code null}. + * @param offset1 the index of the first element to swap + * @param offset2 the index of the second element to swap + * @since 3.5 + */ + public static void swap(final char[] array, final int offset1, final int offset2) { + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given char array. + * + *

This method does nothing for a {@code null} or empty input array or + * for overflow indices. Negative indices are promoted to 0(zero). If any + * of the sub-arrays to swap falls outside of the given array, then the + * swap is stopped at the end of the array and as many as possible elements + * are swapped.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @param array the array to swap, may be {@code null}. + * @param offset1 the index of the first element in the series to swap + * @param offset2 the index of the second element in the series to swap + * @param len the number of elements to swap starting with the given indices + * @since 3.5 + */ + public static void swap(final char[] array, int offset1, int offset2, int len) { + if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) { + return; + } + offset1 = max0(offset1); + offset2 = max0(offset2); + len = Math.min(Math.min(len, array.length - offset1), array.length - offset2); + for (int i = 0; i < len; i++, offset1++, offset2++) { + final char aux = array[offset1]; + array[offset1] = array[offset2]; + array[offset2] = aux; + } + } + + /** + * Swaps two elements in the given double array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for a {@code null} or empty input array or for overflow indices. + * Negative indices are promoted to 0(zero).

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @param array the array to swap, may be {@code null}. + * @param offset1 the index of the first element to swap + * @param offset2 the index of the second element to swap + * @since 3.5 + */ + public static void swap(final double[] array, final int offset1, final int offset2) { + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given double array. + * + *

This method does nothing for a {@code null} or empty input array or + * for overflow indices. Negative indices are promoted to 0(zero). If any + * of the sub-arrays to swap falls outside of the given array, then the + * swap is stopped at the end of the array and as many as possible elements + * are swapped.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @param array the array to swap, may be {@code null}. + * @param offset1 the index of the first element in the series to swap + * @param offset2 the index of the second element in the series to swap + * @param len the number of elements to swap starting with the given indices + * @since 3.5 + */ + public static void swap(final double[] array, int offset1, int offset2, int len) { + if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) { + return; + } + offset1 = max0(offset1); + offset2 = max0(offset2); + len = Math.min(Math.min(len, array.length - offset1), array.length - offset2); + for (int i = 0; i < len; i++, offset1++, offset2++) { + final double aux = array[offset1]; + array[offset1] = array[offset2]; + array[offset2] = aux; + } + } + + /** + * Swaps two elements in the given float array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for a {@code null} or empty input array or for overflow indices. + * Negative indices are promoted to 0(zero).

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @param array the array to swap, may be {@code null}. + * @param offset1 the index of the first element to swap + * @param offset2 the index of the second element to swap + * @since 3.5 + */ + public static void swap(final float[] array, final int offset1, final int offset2) { + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given float array. + * + *

This method does nothing for a {@code null} or empty input array or + * for overflow indices. Negative indices are promoted to 0(zero). If any + * of the sub-arrays to swap falls outside of the given array, then the + * swap is stopped at the end of the array and as many as possible elements + * are swapped.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @param array the array to swap, may be {@code null}. + * @param offset1 the index of the first element in the series to swap + * @param offset2 the index of the second element in the series to swap + * @param len the number of elements to swap starting with the given indices + * @since 3.5 + */ + public static void swap(final float[] array, int offset1, int offset2, int len) { + if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) { + return; + } + offset1 = max0(offset1); + offset2 = max0(offset2); + len = Math.min(Math.min(len, array.length - offset1), array.length - offset2); + for (int i = 0; i < len; i++, offset1++, offset2++) { + final float aux = array[offset1]; + array[offset1] = array[offset2]; + array[offset2] = aux; + } + + } + + /** + * Swaps two elements in the given int array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for a {@code null} or empty input array or for overflow indices. + * Negative indices are promoted to 0(zero).

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @param array the array to swap, may be {@code null} + * @param offset1 the index of the first element to swap + * @param offset2 the index of the second element to swap + * @since 3.5 + */ + public static void swap(final int[] array, final int offset1, final int offset2) { + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given int array. + * + *

This method does nothing for a {@code null} or empty input array or + * for overflow indices. Negative indices are promoted to 0(zero). If any + * of the sub-arrays to swap falls outside of the given array, then the + * swap is stopped at the end of the array and as many as possible elements + * are swapped.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @param array the array to swap, may be {@code null} + * @param offset1 the index of the first element in the series to swap + * @param offset2 the index of the second element in the series to swap + * @param len the number of elements to swap starting with the given indices + * @since 3.5 + */ + public static void swap(final int[] array, int offset1, int offset2, int len) { + if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) { + return; + } + offset1 = max0(offset1); + offset2 = max0(offset2); + len = Math.min(Math.min(len, array.length - offset1), array.length - offset2); + for (int i = 0; i < len; i++, offset1++, offset2++) { + final int aux = array[offset1]; + array[offset1] = array[offset2]; + array[offset2] = aux; + } + } + + /** + * Swaps two elements in the given long array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for a {@code null} or empty input array or for overflow indices. + * Negative indices are promoted to 0(zero).

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([true, false, true], 0, 2) -> [true, false, true]
  • + *
  • ArrayUtils.swap([true, false, true], 0, 0) -> [true, false, true]
  • + *
  • ArrayUtils.swap([true, false, true], 1, 0) -> [false, true, true]
  • + *
  • ArrayUtils.swap([true, false, true], 0, 5) -> [true, false, true]
  • + *
  • ArrayUtils.swap([true, false, true], -1, 1) -> [false, true, true]
  • + *
+ * + * @param array the array to swap, may be {@code null} + * @param offset1 the index of the first element to swap + * @param offset2 the index of the second element to swap + * @since 3.5 + */ + public static void swap(final long[] array, final int offset1, final int offset2) { + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given long array. + * + *

This method does nothing for a {@code null} or empty input array or + * for overflow indices. Negative indices are promoted to 0(zero). If any + * of the sub-arrays to swap falls outside of the given array, then the + * swap is stopped at the end of the array and as many as possible elements + * are swapped.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @param array the array to swap, may be {@code null} + * @param offset1 the index of the first element in the series to swap + * @param offset2 the index of the second element in the series to swap + * @param len the number of elements to swap starting with the given indices + * @since 3.5 + */ + public static void swap(final long[] array, int offset1, int offset2, int len) { + if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) { + return; + } + offset1 = max0(offset1); + offset2 = max0(offset2); + len = Math.min(Math.min(len, array.length - offset1), array.length - offset2); + for (int i = 0; i < len; i++, offset1++, offset2++) { + final long aux = array[offset1]; + array[offset1] = array[offset2]; + array[offset2] = aux; + } + } + + /** + * Swaps two elements in the given array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for a {@code null} or empty input array or for overflow indices. + * Negative indices are promoted to 0(zero).

+ * + * Examples: + *
    + *
  • ArrayUtils.swap(["1", "2", "3"], 0, 2) -> ["3", "2", "1"]
  • + *
  • ArrayUtils.swap(["1", "2", "3"], 0, 0) -> ["1", "2", "3"]
  • + *
  • ArrayUtils.swap(["1", "2", "3"], 1, 0) -> ["2", "1", "3"]
  • + *
  • ArrayUtils.swap(["1", "2", "3"], 0, 5) -> ["1", "2", "3"]
  • + *
  • ArrayUtils.swap(["1", "2", "3"], -1, 1) -> ["2", "1", "3"]
  • + *
+ * + * @param array the array to swap, may be {@code null} + * @param offset1 the index of the first element to swap + * @param offset2 the index of the second element to swap + * @since 3.5 + */ + public static void swap(final Object[] array, final int offset1, final int offset2) { + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given array. + * + *

This method does nothing for a {@code null} or empty input array or + * for overflow indices. Negative indices are promoted to 0(zero). If any + * of the sub-arrays to swap falls outside of the given array, then the + * swap is stopped at the end of the array and as many as possible elements + * are swapped.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap(["1", "2", "3", "4"], 0, 2, 1) -> ["3", "2", "1", "4"]
  • + *
  • ArrayUtils.swap(["1", "2", "3", "4"], 0, 0, 1) -> ["1", "2", "3", "4"]
  • + *
  • ArrayUtils.swap(["1", "2", "3", "4"], 2, 0, 2) -> ["3", "4", "1", "2"]
  • + *
  • ArrayUtils.swap(["1", "2", "3", "4"], -3, 2, 2) -> ["3", "4", "1", "2"]
  • + *
  • ArrayUtils.swap(["1", "2", "3", "4"], 0, 3, 3) -> ["4", "2", "3", "1"]
  • + *
+ * + * @param array the array to swap, may be {@code null} + * @param offset1 the index of the first element in the series to swap + * @param offset2 the index of the second element in the series to swap + * @param len the number of elements to swap starting with the given indices + * @since 3.5 + */ + public static void swap(final Object[] array, int offset1, int offset2, int len) { + if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) { + return; + } + offset1 = max0(offset1); + offset2 = max0(offset2); + len = Math.min(Math.min(len, array.length - offset1), array.length - offset2); + for (int i = 0; i < len; i++, offset1++, offset2++) { + final Object aux = array[offset1]; + array[offset1] = array[offset2]; + array[offset2] = aux; + } + } + + /** + * Swaps two elements in the given short array. + * + *

There is no special handling for multi-dimensional arrays. This method + * does nothing for a {@code null} or empty input array or for overflow indices. + * Negative indices are promoted to 0(zero).

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3], 0, 2) -> [3, 2, 1]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 0) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 1, 0) -> [2, 1, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], 0, 5) -> [1, 2, 3]
  • + *
  • ArrayUtils.swap([1, 2, 3], -1, 1) -> [2, 1, 3]
  • + *
+ * + * @param array the array to swap, may be {@code null} + * @param offset1 the index of the first element to swap + * @param offset2 the index of the second element to swap + * @since 3.5 + */ + public static void swap(final short[] array, final int offset1, final int offset2) { + swap(array, offset1, offset2, 1); + } + + /** + * Swaps a series of elements in the given short array. + * + *

This method does nothing for a {@code null} or empty input array or + * for overflow indices. Negative indices are promoted to 0(zero). If any + * of the sub-arrays to swap falls outside of the given array, then the + * swap is stopped at the end of the array and as many as possible elements + * are swapped.

+ * + * Examples: + *
    + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -> [3, 2, 1, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -> [1, 2, 3, 4]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -> [3, 4, 1, 2]
  • + *
  • ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -> [4, 2, 3, 1]
  • + *
+ * + * @param array the array to swap, may be {@code null} + * @param offset1 the index of the first element in the series to swap + * @param offset2 the index of the second element in the series to swap + * @param len the number of elements to swap starting with the given indices + * @since 3.5 + */ + public static void swap(final short[] array, int offset1, int offset2, int len) { + if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) { + return; + } + offset1 = max0(offset1); + offset2 = max0(offset2); + if (offset1 == offset2) { + return; + } + len = Math.min(Math.min(len, array.length - offset1), array.length - offset2); + for (int i = 0; i < len; i++, offset1++, offset2++) { + final short aux = array[offset1]; + array[offset1] = array[offset2]; + array[offset2] = aux; + } + } + + /** + * 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: + *

+ *
{@code
+     * String[] array = ArrayUtils.toArray("1", "2");
+     * String[] emptyArray = ArrayUtils.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 + * {@code Number[] array = ArrayUtils.toArray(Integer.valueOf(42), Double.valueOf(Math.PI))}, + * there is no real advantage when compared to + * {@code 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 + */ + @SuppressWarnings("unchecked") + public static T[] toArray(final T... items) { + return items; + } + + /** + * 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 = ArrayUtils.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 {@link Map} that was created from the array + * @throws IllegalArgumentException if one element of this Array is + * itself an Array containing less than 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; + } + + /** + * 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 {@link Boolean} array, {@code null} if null array input + */ + public static Boolean[] toObject(final boolean[] array) { + if (array == null) { + return null; + } + if (array.length == 0) { + return EMPTY_BOOLEAN_OBJECT_ARRAY; + } + return setAll(new Boolean[array.length], i -> array[i] ? Boolean.TRUE : Boolean.FALSE); + } + /** - * An empty immutable {@code double} array. + * 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 {@link Byte} array, {@code null} if null array input */ - public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; + public static Byte[] toObject(final byte[] array) { + if (array == null) { + return null; + } + if (array.length == 0) { + return EMPTY_BYTE_OBJECT_ARRAY; + } + return setAll(new Byte[array.length], i -> Byte.valueOf(array[i])); + } + /** - * An empty immutable {@code float} array. + * 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 {@link Character} array, {@code null} if null array input */ - public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; - - // NOTE: Cannot use {@code} to enclose text which includes {}, but is OK - - // nullToEmpty - //----------------------------------------------------------------------- + public static Character[] toObject(final char[] array) { + if (array == null) { + return null; + } + if (array.length == 0) { + return EMPTY_CHARACTER_OBJECT_ARRAY; + } + return setAll(new Character[array.length], i -> Character.valueOf(array[i])); + } /** - *

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. + * Converts an array of primitive doubles to objects. * - *

As a memory optimizing technique an empty array passed in will be overridden with - * the empty {@code public static} references in this class. + *

This method returns {@code null} for a {@code null} input array.

* - * @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 + * @param array a {@code double} array + * @return a {@link Double} array, {@code null} if null array input */ - public static Object[] nullToEmpty(final Object[] array) { - if (isEmpty(array)) { - return EMPTY_OBJECT_ARRAY; + public static Double[] toObject(final double[] array) { + if (array == null) { + return null; } - return array; + if (array.length == 0) { + return EMPTY_DOUBLE_OBJECT_ARRAY; + } + return setAll(new Double[array.length], i -> Double.valueOf(array[i])); } /** - *

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. + * Converts an array of primitive floats to objects. * - *

As a memory optimizing technique an empty array passed in will be overridden with - * the empty {@code public static} references in this class. + *

This method returns {@code null} for a {@code null} input array.

* - * @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 + * @param array a {@code float} array + * @return a {@link Float} array, {@code null} if null array input */ - public static Class[] nullToEmpty(final Class[] array) { - if (isEmpty(array)) { - return EMPTY_CLASS_ARRAY; + public static Float[] toObject(final float[] array) { + if (array == null) { + return null; } - return array; + if (array.length == 0) { + return EMPTY_FLOAT_OBJECT_ARRAY; + } + return setAll(new Float[array.length], i -> Float.valueOf(array[i])); } - // Is same length - //----------------------------------------------------------------------- /** - *

Checks whether two arrays are the same length, treating - * {@code null} arrays as length {@code 0}. + * Converts an array of primitive ints to objects. * - *

Any multi-dimensional aspects of the arrays are ignored. + *

This method returns {@code null} for a {@code null} input array.

* - * @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 + * @param array an {@code int} array + * @return an {@link Integer} array, {@code null} if null array input */ - public static boolean isSameLength(final Object[] array1, final Object[] array2) { - return getLength(array1) == getLength(array2); + public static Integer[] toObject(final int[] array) { + if (array == null) { + return null; + } + if (array.length == 0) { + return EMPTY_INTEGER_OBJECT_ARRAY; + } + return setAll(new Integer[array.length], i -> Integer.valueOf(array[i])); } - //----------------------------------------------------------------------- /** - *

Returns the length of the specified array. - * This method can deal with {@code Object} arrays and with primitive arrays. + * Converts an array of primitive longs to objects. * - *

If the input array is {@code null}, {@code 0} is returned. + *

This method returns {@code null} for a {@code null} input array.

* - *
-     * 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 a {@code long} array + * @return a {@link Long} array, {@code null} if null array input + */ + public static Long[] toObject(final long[] array) { + if (array == null) { + return null; + } + if (array.length == 0) { + return EMPTY_LONG_OBJECT_ARRAY; + } + return setAll(new Long[array.length], i -> Long.valueOf(array[i])); + } + + /** + * Converts an array of primitive shorts to objects. * - * @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 + *

This method returns {@code null} for a {@code null} input array.

+ * + * @param array a {@code short} array + * @return a {@link Short} array, {@code null} if null array input */ - public static int getLength(final Object array) { + public static Short[] toObject(final short[] array) { if (array == null) { - return 0; + return null; + } + if (array.length == 0) { + return EMPTY_SHORT_OBJECT_ARRAY; } - return Array.getLength(array); + return setAll(new Short[array.length], i -> Short.valueOf(array[i])); } - // Long array converters - // ---------------------------------------------------------------------- /** - *

Converts an array of object Longs to primitives. + * Converts an array of object Booleans to primitives. + *

+ * This method returns {@code null} for a {@code null} input array. + *

+ *

+ * Null array elements map to false, like {@code Boolean.parseBoolean(null)} and its callers return false. + *

* - *

This method returns {@code null} for a {@code null} input array. + * @param array a {@link Boolean} array, may be {@code null} + * @return a {@code boolean} array, {@code null} if null array input + */ + public static boolean[] toPrimitive(final Boolean[] array) { + return toPrimitive(array, false); + } + + /** + * 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 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} + * @param array a {@link 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 long[] toPrimitive(final Long[] array) { + public static boolean[] toPrimitive(final Boolean[] array, final boolean valueForNull) { if (array == null) { return null; - } else if (array.length == 0) { - return EMPTY_LONG_ARRAY; } - final long[] result = new long[array.length]; + 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].longValue(); + final Boolean b = array[i]; + result[i] = b == null ? valueForNull : b.booleanValue(); } return result; } - // Int array converters - // ---------------------------------------------------------------------- /** - *

Converts an array of object Integers to primitives. + * Converts an array of object Bytes to primitives. + *

+ * This method returns {@code null} for a {@code null} input array. + *

* - *

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} + * @param array a {@link Byte} array, may be {@code null} + * @return a {@code byte} array, {@code null} if null array input + * @throws NullPointerException if an array element is {@code null} */ - public static int[] toPrimitive(final Integer[] array) { + public static byte[] toPrimitive(final Byte[] array) { if (array == null) { return null; - } else if (array.length == 0) { - return EMPTY_INT_ARRAY; } - final int[] result = new int[array.length]; + 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].intValue(); + result[i] = array[i].byteValue(); } return result; } - // Short array converters - // ---------------------------------------------------------------------- /** - *

Converts an array of object Shorts to primitives. + * Converts an array of object Bytes to primitives handling {@code null}. + *

+ * This method returns {@code null} for a {@code null} input array. + *

* - *

This method returns {@code null} for a {@code null} input array. - * - * @param array a {@code Short} array, may be {@code null} + * @param array a {@link 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 - * @throws NullPointerException if array content is {@code null} */ - public static short[] toPrimitive(final Short[] array) { + public static byte[] toPrimitive(final Byte[] array, final byte valueForNull) { if (array == null) { return null; - } else if (array.length == 0) { - return EMPTY_SHORT_ARRAY; } - final short[] result = new short[array.length]; + 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].shortValue(); + final Byte b = array[i]; + result[i] = b == null ? valueForNull : b.byteValue(); } return result; } - // Byte array converters - // ---------------------------------------------------------------------- + /** + * Converts an array of object Characters to primitives. + *

+ * This method returns {@code null} for a {@code null} input array. + *

+ * + * @param array a {@link Character} array, may be {@code null} + * @return a {@code char} array, {@code null} if null array input + * @throws NullPointerException if an array element is {@code null} + */ + public static char[] toPrimitive(final Character[] array) { + if (array == null) { + return null; + } + 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; + } - // Double array converters - // ---------------------------------------------------------------------- /** - *

Converts an array of object Doubles to primitives. + * Converts an array of object Character to primitives handling {@code null}. + *

+ * This method returns {@code null} for a {@code null} input array. + *

* - *

This method returns {@code null} for a {@code null} input array. + * @param array a {@link 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; + } + 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 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} + * @param array a {@link 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} + * @throws NullPointerException if an array element is {@code null} */ public static double[] toPrimitive(final Double[] array) { if (array == null) { return null; - } else if (array.length == 0) { + } + if (array.length == 0) { return EMPTY_DOUBLE_ARRAY; } final double[] result = new double[array.length]; @@ -255,21 +7471,46 @@ public static double[] toPrimitive(final Double[] array) { return result; } - // Float array converters - // ---------------------------------------------------------------------- /** - *

Converts an array of object Floats to primitives. + * Converts an array of object Doubles to primitives handling {@code null}. + *

+ * This method returns {@code null} for a {@code null} input array. + *

* - *

This method returns {@code null} for a {@code null} input array. + * @param array a {@link 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; + } + 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 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} + * @param array a {@link 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} + * @throws NullPointerException if an array element is {@code null} */ public static float[] toPrimitive(final Float[] array) { if (array == null) { return null; - } else if (array.length == 0) { + } + if (array.length == 0) { return EMPTY_FLOAT_ARRAY; } final float[] result = new float[array.length]; @@ -280,9 +7521,133 @@ public static float[] toPrimitive(final Float[] array) { } /** - *

Create an array of primitive type from an array of wrapper types. + * 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 {@link 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; + } + 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 object Integers to primitives. + *

+ * This method returns {@code null} for a {@code null} input array. + *

+ * + * @param array a {@link Integer} array, may be {@code null} + * @return an {@code int} array, {@code null} if null array input + * @throws NullPointerException if an array element is {@code null} + */ + public static int[] toPrimitive(final Integer[] array) { + if (array == null) { + return null; + } + 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 {@link 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; + } + 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 object Longs to primitives. + *

+ * This method returns {@code null} for a {@code null} input array. + *

+ * + * @param array a {@link Long} array, may be {@code null} + * @return a {@code long} array, {@code null} if null array input + * @throws NullPointerException if an array element is {@code null} + */ + public static long[] toPrimitive(final Long[] array) { + if (array == null) { + return null; + } + 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. + *

* - *

This method returns {@code null} for a {@code null} input array. + * @param array a {@link 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; + } + 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; + } + + /** + * Create an array of primitive type from an array of wrapper types. + *

+ * This method returns {@code null} for a {@code null} input array. + *

* * @param array an array of wrapper object * @return an array of the corresponding primitive type, or the original array @@ -294,34 +7659,79 @@ public static Object toPrimitive(final Object array) { } final Class ct = array.getClass().getComponentType(); final Class pt = ClassUtils.wrapperToPrimitive(ct); - if(Integer.TYPE.equals(pt)) { + if (Boolean.TYPE.equals(pt)) { + return toPrimitive((Boolean[]) array); + } + if (Character.TYPE.equals(pt)) { + return toPrimitive((Character[]) array); + } + if (Byte.TYPE.equals(pt)) { + return toPrimitive((Byte[]) array); + } + if (Integer.TYPE.equals(pt)) { return toPrimitive((Integer[]) array); } - if(Long.TYPE.equals(pt)) { + if (Long.TYPE.equals(pt)) { return toPrimitive((Long[]) array); } - if(Short.TYPE.equals(pt)) { + if (Short.TYPE.equals(pt)) { return toPrimitive((Short[]) array); } - if(Double.TYPE.equals(pt)) { + if (Double.TYPE.equals(pt)) { return toPrimitive((Double[]) array); } - if(Float.TYPE.equals(pt)) { + if (Float.TYPE.equals(pt)) { return toPrimitive((Float[]) array); } return array; } - // ---------------------------------------------------------------------- /** - *

Checks if an array of Objects is empty or {@code null}. + * Converts an array of object Shorts to primitives. + *

+ * This method returns {@code null} for a {@code null} input array. + *

* - * @param array the array to test - * @return {@code true} if the array is empty or {@code null} - * @since 2.1 + * @param array a {@link Short} array, may be {@code null} + * @return a {@code byte} array, {@code null} if null array input + * @throws NullPointerException if an array element is {@code null} */ - public static boolean isEmpty(final Object[] array) { - return getLength(array) == 0; + public static short[] toPrimitive(final Short[] array) { + if (array == null) { + return null; + } + 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 {@link 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; + } + 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; + } +} \ No newline at end of file diff --git a/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/ClassUtils.java b/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/ClassUtils.java index 8d9c7a9a7d..6ecd42f8e8 100644 --- a/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/ClassUtils.java +++ b/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/ClassUtils.java @@ -21,152 +21,453 @@ */ package org.ehcache.impl.internal.classes.commonslang; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +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 java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; /** - *

Operates on classes without using reflection.

+ * 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.

+ *

+ * This class handles invalid {@code null} inputs as best it can. Each method documents its behavior 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}.

+ *

+ * 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 */ public class ClassUtils { /** - * Maps names of primitives to their corresponding primitive {@code Class}es. + * Inclusivity literals for {@link #hierarchy(Class, Interfaces)}. + * + * @since 3.2 + */ + public enum Interfaces { + + /** Includes interfaces. */ + INCLUDE, + + /** Excludes interfaces. */ + EXCLUDE + } + + /** + * The package separator character: {@code '.' == {@value}}. + */ + public static final char PACKAGE_SEPARATOR_CHAR = '.'; + + /** + * The package separator String: {@code "."}. + */ + public static final String PACKAGE_SEPARATOR = String.valueOf(PACKAGE_SEPARATOR_CHAR); + + /** + * The inner class separator character: {@code '$' == {@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 names of primitives to their corresponding primitive {@link Class}es. */ - private static final Map> namePrimitiveMap = new HashMap<>(); + private static final Map> NAME_PRIMITIVE_MAP = new HashMap<>(); + static { - namePrimitiveMap.put("boolean", Boolean.TYPE); - namePrimitiveMap.put("byte", Byte.TYPE); - namePrimitiveMap.put("char", Character.TYPE); - namePrimitiveMap.put("short", Short.TYPE); - namePrimitiveMap.put("int", Integer.TYPE); - namePrimitiveMap.put("long", Long.TYPE); - namePrimitiveMap.put("double", Double.TYPE); - namePrimitiveMap.put("float", Float.TYPE); - namePrimitiveMap.put("void", Void.TYPE); + NAME_PRIMITIVE_MAP.put(Boolean.TYPE.getName(), Boolean.TYPE); + NAME_PRIMITIVE_MAP.put(Byte.TYPE.getName(), Byte.TYPE); + NAME_PRIMITIVE_MAP.put(Character.TYPE.getName(), Character.TYPE); + NAME_PRIMITIVE_MAP.put(Double.TYPE.getName(), Double.TYPE); + NAME_PRIMITIVE_MAP.put(Float.TYPE.getName(), Float.TYPE); + NAME_PRIMITIVE_MAP.put(Integer.TYPE.getName(), Integer.TYPE); + NAME_PRIMITIVE_MAP.put(Long.TYPE.getName(), Long.TYPE); + NAME_PRIMITIVE_MAP.put(Short.TYPE.getName(), Short.TYPE); + NAME_PRIMITIVE_MAP.put(Void.TYPE.getName(), Void.TYPE); } /** - * Maps primitive {@code Class}es to their corresponding wrapper {@code Class}. + * Maps primitive {@link Class}es to their corresponding wrapper {@link Class}. */ - private static final Map, Class> primitiveWrapperMap = new HashMap<>(); + private static final Map, Class> PRIMITIVE_WRAPPER_MAP = new HashMap<>(); + 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); + PRIMITIVE_WRAPPER_MAP.put(Boolean.TYPE, Boolean.class); + PRIMITIVE_WRAPPER_MAP.put(Byte.TYPE, Byte.class); + PRIMITIVE_WRAPPER_MAP.put(Character.TYPE, Character.class); + PRIMITIVE_WRAPPER_MAP.put(Short.TYPE, Short.class); + PRIMITIVE_WRAPPER_MAP.put(Integer.TYPE, Integer.class); + PRIMITIVE_WRAPPER_MAP.put(Long.TYPE, Long.class); + PRIMITIVE_WRAPPER_MAP.put(Double.TYPE, Double.class); + PRIMITIVE_WRAPPER_MAP.put(Float.TYPE, Float.class); + PRIMITIVE_WRAPPER_MAP.put(Void.TYPE, Void.TYPE); } /** - * Maps wrapper {@code Class}es to their corresponding primitive types. + * Maps wrapper {@link Class}es to their corresponding primitive types. */ - private static final Map, Class> wrapperPrimitiveMap = new HashMap<>(); + private static final Map, Class> WRAPPER_PRIMITIVE_MAP = new HashMap<>(); + static { - for (final Map.Entry, Class> entry : primitiveWrapperMap.entrySet()) { - final Class primitiveClass = entry.getKey(); - final Class wrapperClass = entry.getValue(); + PRIMITIVE_WRAPPER_MAP.forEach((primitiveClass, wrapperClass) -> { if (!primitiveClass.equals(wrapperClass)) { - wrapperPrimitiveMap.put(wrapperClass, primitiveClass); + WRAPPER_PRIMITIVE_MAP.put(wrapperClass, primitiveClass); } - } + }); } - // Is assignable - // ---------------------------------------------------------------------- + /** + * Given a {@link List} of {@link Class} objects, this method converts them into class names. + * + *

+ * A new {@link List} is returned. {@code null} objects will be copied into the returned list as {@code null}. + *

+ * + * @param classes the classes to change + * @return a {@link List} of class names corresponding to the Class objects, {@code null} if null input + * @throws ClassCastException if {@code classes} contains a non-{@link Class} entry + */ + public static List convertClassesToClassNames(final List> classes) { + return classes == null ? null : classes.stream().map(e -> getName(e, null)).collect(Collectors.toList()); + } /** - *

Checks if an array of Classes can be assigned to another array of Classes.

+ * Given a {@link List} of class names, this method converts them into 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).

+ *

+ * A new {@link List} is returned. If the class name cannot be found, {@code null} is stored in the {@link List}. If the + * class name in the {@link List} is {@code null}, {@code null} is stored in the output {@link List}. + *

* - *

Unlike the {@link Class#isAssignableFrom(Class)} method, this - * method takes into account widenings of primitive classes and - * {@code null}s.

+ * @param classNames the classNames to change + * @return a {@link 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()); + classNames.forEach(className -> { + try { + classes.add(Class.forName(className)); + } catch (final Exception ex) { + classes.add(null); + } + }); + return classes; + } + + /** + * Gets a {@link List} of all interfaces implemented by the given class and its superclasses. * - *

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.

+ *

+ * 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. + *

* - *

{@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.

+ * @param cls the class to look up, may be {@code null} + * @return the {@link 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); + } + + /** + * Gets the interfaces for the specified class. * - *

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 look up, may be {@code null} + * @param interfacesFound the {@link 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(); + } + } + + /** + * Gets a {@link List} of superclasses for the given class. * - * @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 + * @param cls the class to look up, may be {@code null} + * @return the {@link List} of superclasses in order going up from this one {@code null} if null input */ - public static boolean isAssignable(Class[] classArray, Class[] toClassArray, final boolean autoboxing) { - if (!ArrayUtils.isSameLength(classArray, toClassArray)) { - return false; + public static List> getAllSuperclasses(final Class cls) { + if (cls == null) { + return null; } - if (classArray == null) { - classArray = ArrayUtils.EMPTY_CLASS_ARRAY; + final List> classes = new ArrayList<>(); + Class superclass = cls.getSuperclass(); + while (superclass != null) { + classes.add(superclass); + superclass = superclass.getSuperclass(); } - if (toClassArray == null) { - toClassArray = ArrayUtils.EMPTY_CLASS_ARRAY; + return classes; + } + + /** + * Gets the canonical name for a {@link Class}. + * + * @param cls the class for which to get the canonical class name; may be null + * @param valueIfNull the return value if null + * @return the canonical name of the class, or {@code valueIfNull} + * @since 3.7 + * @see Class#getCanonicalName() + */ + public static String getCanonicalName(final Class cls, final String valueIfNull) { + if (cls == null) { + return valueIfNull; } - for (int i = 0; i < classArray.length; i++) { - if (!isAssignable(classArray[i], toClassArray[i], autoboxing)) { - return false; - } + final String canonicalName = cls.getCanonicalName(); + return canonicalName == null ? valueIfNull : canonicalName; + } + + /** + * Gets the canonical name for an {@link Object}. + * + * @param object the object for which to get the canonical class name; may be null + * @param valueIfNull the return value if null + * @return the canonical name of the object or {@code valueIfNull} + * @since 3.7 + * @see Class#getCanonicalName() + */ + public static String getCanonicalName(final Object object, final String valueIfNull) { + if (object == null) { + return valueIfNull; } - return true; + final String canonicalName = object.getClass().getCanonicalName(); + return canonicalName == null ? valueIfNull : canonicalName; + } + + /** + * Delegates to {@link Class#getComponentType()} using generics. + * + * @param The array class type. + * @param cls A class or null. + * @return The array component type or null. + * @see Class#getComponentType() + * @since 3.13.0 + */ + @SuppressWarnings("unchecked") + public static Class getComponentType(final Class cls) { + return cls == null ? null : (Class) cls.getComponentType(); } /** - *

Checks if one {@code Class} can be assigned to a variable of - * another {@code Class}.

+ * Null-safe version of {@code cls.getName()} * - *

Unlike the {@link Class#isAssignableFrom(Class)} method, - * this method takes into account widenings of primitive classes and - * {@code null}s.

+ * @param cls the class for which to get the class name; may be null + * @param valueIfNull the return value if the argument {@code cls} is {@code null} + * @return the class name or {@code valueIfNull} + * @since 3.7 + * @see Class#getName() + */ + public static String getName(final Class cls, final String valueIfNull) { + return cls == null ? valueIfNull : cls.getName(); + } + + /** + * Null-safe version of {@code object.getClass().getSimpleName()} + * + * @param object the object for which to get the class name; may be null + * @param valueIfNull the value to return if {@code object} is {@code null} + * @return the class name or {@code valueIfNull} + * @since 3.0 + * @see Class#getName() + */ + public static String getName(final Object object, final String valueIfNull) { + return object == null ? valueIfNull : object.getClass().getName(); + } + + /** + * Gets the primitive class for the given class name, for example "byte". * - *

Primitive widenings allow an int to be assigned to a long, float or - * double. This method returns the correct result for these cases.

+ * @param className the primitive class for the given class name. + * @return the primitive class. + */ + static Class getPrimitiveClass(final String className) { + return NAME_PRIMITIVE_MAP.get(className); + } + + /** + * Null-safe version of {@code cls.getSimpleName()} * - *

{@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.

+ * @param cls the class for which to get the simple name; may be null + * @param valueIfNull the value to return if null + * @return the simple class name or {@code valueIfNull} if the argument {@code cls} is {@code null} + * @since 3.0 + * @see Class#getSimpleName() + */ + public static String getSimpleName(final Class cls, final String valueIfNull) { + return cls == null ? valueIfNull : cls.getSimpleName(); + } + + /** + * Null-safe version of {@code object.getClass().getSimpleName()} * - *

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 object the object for which to get the simple class name; may be null + * @param valueIfNull the value to return if {@code object} is {@code null} + * @return the simple class name or {@code valueIfNull} if the argument {@code object} is {@code null} + * @since 3.0 + * @see Class#getSimpleName() + */ + public static String getSimpleName(final Object object, final String valueIfNull) { + return object == null ? valueIfNull : object.getClass().getSimpleName(); + } + + /** + * Gets an {@link Iterable} that can iterate over a class hierarchy in ascending (subclass to superclass) order, + * excluding interfaces. * - *

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 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); + } + + /** + * Gets an {@link Iterable} that can iterate over a class hierarchy in ascending (subclass to superclass) order. * - * @param cls the Class to check, may be null - * @param toClass the Class to try to assign into, returns false if null + * @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 = () -> { + final AtomicReference> next = new AtomicReference<>(type); + return new Iterator>() { + + @Override + public boolean hasNext() { + return next.get() != null; + } + + @Override + public Class next() { + return next.getAndUpdate(Class::getSuperclass); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + }; + if (interfacesBehavior != Interfaces.INCLUDE) { + return classes; + } + return () -> { + final Set> seenInterfaces = new HashSet<>(); + final Iterator> wrapped = classes.iterator(); + + return new Iterator>() { + Iterator> interfaces = Collections.emptyIterator(); + + @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; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + 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); + } + } + + }; + }; + } + + /** + * Checks if one {@link Class} can be assigned to a variable of another {@link 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 {@link Class} parameter can be + * converted to the type represented by this {@link 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) { @@ -174,30 +475,33 @@ public static boolean isAssignable(final Class cls, final Class toClass) { } /** - *

Checks if one {@code Class} can be assigned to a variable of - * another {@code Class}.

+ * Checks if one {@link Class} can be assigned to a variable of another {@link Class}. * - *

Unlike the {@link Class#isAssignableFrom(Class)} method, - * this method takes into account widenings of primitive classes and - * {@code null}s.

+ *

+ * 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.

+ *

+ * 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.

+ *

+ * {@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.

+ *

+ * Specifically, this method tests whether the type represented by the specified {@link Class} parameter can be + * converted to the type represented by this {@link 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 + * @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) { @@ -208,7 +512,7 @@ public static boolean isAssignable(Class cls, final Class toClass, final b if (cls == null) { return !toClass.isPrimitive(); } - //autoboxing: + // autoboxing: if (autoboxing) { if (cls.isPrimitive() && !toClass.isPrimitive()) { cls = primitiveToWrapper(cls); @@ -231,13 +535,10 @@ public static boolean isAssignable(Class cls, final Class toClass, final b return false; } if (Integer.TYPE.equals(cls)) { - return Long.TYPE.equals(toClass) - || Float.TYPE.equals(toClass) - || Double.TYPE.equals(toClass); + 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); + return Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass); } if (Boolean.TYPE.equals(cls)) { return false; @@ -248,23 +549,11 @@ public static boolean isAssignable(Class cls, final Class toClass, final b 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 (Character.TYPE.equals(cls) || 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) + 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 @@ -274,68 +563,300 @@ public static boolean isAssignable(Class cls, final Class toClass, final b } /** - *

Converts the specified primitive Class object to its corresponding - * wrapper Class object.

+ * 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 {@link Class} parameter can be + * converted to the type represented by this {@link 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, true); + } + + /** + * 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 {@link Class} parameter can be + * converted to the type represented by this {@link 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)) { + return false; + } + classArray = ArrayUtils.nullToEmpty(classArray); + toClassArray = ArrayUtils.nullToEmpty(toClassArray); + for (int i = 0; i < classArray.length; i++) { + if (!isAssignable(classArray[i], toClassArray[i], autoboxing)) { + return false; + } + } + return true; + } + + /** + * Is the specified class an inner class or static nested class. * - *

NOTE: From v2.2, this method handles {@code Void.TYPE}, - * returning {@code Void.TYPE}.

+ * @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; + } + + /** + * 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 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. + * @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); + } + /** + * Tests 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 WRAPPER_PRIMITIVE_MAP.containsKey(type); + } + + /** + * Tests whether a {@link Class} is public. + * @param cls Class to test. + * @return {@code true} if {@code cls} is public. + * @since 3.13.0 + */ + public static boolean isPublic(final Class cls) { + return Modifier.isPublic(cls.getModifiers()); + } + + /** + * 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 + */ + @SuppressWarnings("rawtypes") + 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]; + Arrays.setAll(convertedClasses, i -> primitiveToWrapper(classes[i])); + return convertedClasses; + } + + /** + * 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); + convertedClass = PRIMITIVE_WRAPPER_MAP.get(cls); } return convertedClass; } /** - *

Converts the specified wrapper class to its corresponding primitive - * class.

+ * Converts an array of {@link Object} in to an array of {@link Class} objects. If any of these objects is null, a null + * element will be inserted into the array. * - *

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.

+ *

+ * This method returns {@code null} for a {@code null} input array. + *

* - * @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) + * @param array an {@link Object} array + * @return a {@link Class} array, {@code null} if null array input * @since 2.4 */ - public static Class wrapperToPrimitive(final Class cls) { - return wrapperPrimitiveMap.get(cls); + @SuppressWarnings("rawtypes") + public static Class[] toClass(final Object... array) { + if (array == null) { + return null; + } + if (array.length == 0) { + return ArrayUtils.EMPTY_CLASS_ARRAY; + } + final Class[] classes = new Class[array.length]; + Arrays.setAll(classes, i -> array[i] == null ? null : array[i].getClass()); + return classes; } - // ---------------------------------------------------------------------- + /** + * Decides if the part that was just copied to its destination location in the work array can be kept as it was copied + * or must be abbreviated. It must be kept when the part is the last one, which is the simple name of the class. In this + * case the {@code source} index, from where the characters are copied points one position after the last character, + * a.k.a. {@code source == + * originalLength} + * + *

+ * If the part is not the last one then it can be kept unabridged if the number of the characters copied so far plus the + * character that are to be copied is less than or equal to the desired length. + *

+ * + * @param runAheadTarget the target index (where the characters were copied to) pointing after the last character copied + * when the current part was copied + * @param source the source index (where the characters were copied from) pointing after the last character copied when + * the current part was copied + * @param originalLength the original length of the class full name, which is abbreviated + * @param desiredLength the desired length of the abbreviated class name + * @return {@code true} if it can be kept in its original length {@code false} if the current part has to be abbreviated + * and + */ + private static boolean useFull(final int runAheadTarget, final int source, final int originalLength, final int desiredLength) { + return source >= originalLength || runAheadTarget + originalLength - source <= desiredLength; + } /** - *

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.

+ * Converts the specified array of wrapper Class objects to an array of its corresponding primitive Class objects. * - *

This method returns {@code null} for a {@code null} input array.

+ *

+ * This method invokes {@code wrapperToPrimitive()} for each element of the passed in array. + *

* - * @param array an {@code Object} array - * @return a {@code Class} array, {@code null} if null array input + * @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[] toClass(final Object... array) { - if (array == null) { + @SuppressWarnings("rawtypes") + public static Class[] wrappersToPrimitives(final Class... classes) { + if (classes == 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(); + + if (classes.length == 0) { + return classes; } - return classes; + + final Class[] convertedClasses = new Class[classes.length]; + Arrays.setAll(convertedClasses, i -> wrapperToPrimitive(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 WRAPPER_PRIMITIVE_MAP.get(cls); + } + + /** + * 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. + *

+ * + * @deprecated TODO Make private in 4.0. + */ + @Deprecated + public ClassUtils() { + // empty } -} +} \ No newline at end of file diff --git a/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/ConstructorUtils.java b/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/ConstructorUtils.java index 25e11e82e9..1d4f51de22 100644 --- a/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/ConstructorUtils.java +++ b/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/ConstructorUtils.java @@ -26,149 +26,110 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; import java.util.Objects; /** - *

Utility reflection methods focused on constructors, modeled after - * {@link MethodUtils}.

+ * 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.

+ *

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.

+ *

+ * {@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 */ public class ConstructorUtils { /** - *

Returns a new instance of the specified class inferring the right constructor - * from the types of the arguments.

+ * Finds a constructor given a class and signature, checking accessibility. * - *

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} + *

+ * 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. * @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(Class, Object[], Class[]) + * @throws SecurityException Thrown if a security manager is present and the caller's class loader is not the same as or an ancestor of the class loader + * for the class and invocation of {@link SecurityManager#checkPackageAccess(String)} denies access to the package of the + * class. + * @see Class#getConstructor + * @see #getAccessibleConstructor(java.lang.reflect.Constructor) */ - 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()); - } - if (ctor.isVarArgs()) { - final Class[] methodParameterTypes = ctor.getParameterTypes(); - args = MethodUtils.getVarArgs(args, methodParameterTypes); + public static Constructor getAccessibleConstructor(final Class cls, final Class... parameterTypes) { + Objects.requireNonNull(cls, "cls"); + try { + return getAccessibleConstructor(cls.getConstructor(parameterTypes)); + } catch (final NoSuchMethodException e) { + return null; } - return ctor.newInstance(args); } - //----------------------------------------------------------------------- - /** - *

Checks if the specified constructor is accessible.

+ * Checks if the specified constructor is accessible. * - *

This simply ensures that the 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 + * @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 SecurityManager * @throws NullPointerException if {@code ctor} is {@code null} + * @throws SecurityException Thrown if a security manager is present and a caller's class loader is not the same as or an ancestor of the class loader + * for a class and invocation of {@link SecurityManager#checkPackageAccess(String)} denies access to the package of the class. */ public static Constructor getAccessibleConstructor(final Constructor ctor) { - Objects.requireNonNull(ctor, "constructor cannot be null"); - return MemberUtils.isAccessible(ctor) - && isAccessible(ctor.getDeclaringClass()) ? ctor : null; + Objects.requireNonNull(ctor, "ctor"); + return MemberUtils.isAccessible(ctor) && isAccessible(ctor.getDeclaringClass()) ? ctor : null; } /** - *

Finds an accessible constructor with compatible parameters.

+ * 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.

+ *

+ * 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. + *

* - *

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} + * @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 Thrown if {@code cls} is {@code null} + * @throws SecurityException Thrown if a security manager is present and the caller's class loader is not the same as or an ancestor of the class loader for the + * class and invocation of {@link SecurityManager#checkPackageAccess(String)} denies access to the package of the class. + * @see SecurityManager#checkPackageAccess(String) */ - public static Constructor getMatchingAccessibleConstructor(final Class cls, - final Class... parameterTypes) { - Objects.requireNonNull(cls, "class cannot be null"); + public static Constructor getMatchingAccessibleConstructor(final Class cls, final Class... parameterTypes) { + Objects.requireNonNull(cls, "cls"); // 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 + return MemberUtils.setAccessibleWorkaround(cls.getConstructor(parameterTypes)); + } catch (final NoSuchMethodException ignored) { + // ignore } Constructor result = null; /* - * (1) Class.getConstructors() is documented to return Constructor so as - * long as the array is not subsequently modified, everything's fine. + * (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 @@ -180,8 +141,7 @@ public static Constructor getMatchingAccessibleConstructor(final Class if (result == null || MemberUtils.compareConstructorFit(ctor, result, parameterTypes) < 0) { // temporary variable for annotation, see comment above (1) @SuppressWarnings("unchecked") - final - Constructor constructor = (Constructor)ctor; + final Constructor constructor = (Constructor) ctor; result = constructor; } } @@ -191,16 +151,172 @@ public static Constructor getMatchingAccessibleConstructor(final Class } /** - * 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}. + * 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 Thrown if {@code cls} is {@code null}. + * @throws NoSuchMethodException Thrown if a matching constructor cannot be found. + * @throws IllegalAccessException Thrown if the found {@code Constructor} is enforcing Java language access control and the underlying constructor is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the number of actual and formal parameters differ; or
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value cannot be converted to the corresponding formal parameter type by a + * method invocation conversion; if this constructor pertains to an enum type. + *
+ * @throws InstantiationException Thrown if the class that declares the underlying constructor represents an abstract class. + * @throws InvocationTargetException Thrown if the underlying constructor throws an exception. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + * @throws SecurityException Thrown if a security manager is present and the caller's class loader is not the same as or an ancestor of the class + * loader for the class and invocation of {@link SecurityManager#checkPackageAccess(String)} denies access to the + * package of the class. + * @see #invokeConstructor(Class, Object[], Class[]) + */ + public static T invokeConstructor(final Class cls, final Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + final Object[] actuals = ArrayUtils.nullToEmpty(args); + return invokeConstructor(cls, actuals, ClassUtils.toClass(actuals)); + } + + /** + * 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 Thrown if {@code cls} is {@code null}. + * @throws NoSuchMethodException Thrown if a matching constructor cannot be found. + * @throws IllegalAccessException Thrown if the found {@code Constructor} is enforcing Java language access control and the underlying constructor is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the number of actual and formal parameters differ; or
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value cannot be converted to the corresponding formal parameter type by a + * method invocation conversion; if this constructor pertains to an enum type. + *
+ * @throws InstantiationException Thrown if the class that declares the underlying constructor represents an abstract class. + * @throws InvocationTargetException Thrown if the underlying constructor throws an exception. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + * @throws SecurityException Thrown if a security manager is present and the caller's class loader is not the same as or an ancestor of the class + * loader for the class and invocation of {@link SecurityManager#checkPackageAccess(String)} denies access to the + * package of the class. + * @see Constructor#newInstance(Object...) + * @see Constructor#newInstance + */ + public static T invokeConstructor(final Class cls, final Object[] args, final Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + final Object[] actuals = ArrayUtils.nullToEmpty(args); + final Constructor ctor = getMatchingAccessibleConstructor(cls, ArrayUtils.nullToEmpty(parameterTypes)); + if (ctor == null) { + throw new NoSuchMethodException("No such accessible constructor on object: " + cls.getName()); + } + return ctor.newInstance(ctor.isVarArgs() ? MethodUtils.getVarArgs(actuals, ctor.getParameterTypes()) : actuals); + } + + /** + * 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 Thrown if {@code cls} is {@code null}. + * @throws NoSuchMethodException Thrown if a matching constructor cannot be found. + * @throws IllegalAccessException Thrown if the found {@code Constructor} is enforcing Java language access control and the underlying constructor is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the number of actual and formal parameters differ; or
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value cannot be converted to the corresponding formal parameter type by a + * method invocation conversion; if this constructor pertains to an enum type. + *
+ * @throws InstantiationException Thrown if the class that declares the underlying constructor represents an abstract class. + * @throws InvocationTargetException Thrown if the underlying constructor throws an exception. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + * @throws SecurityException Thrown if a security manager is present and the caller's class loader is not the same as or an ancestor of the class + * loader for the class and invocation of {@link SecurityManager#checkPackageAccess(String)} denies access to the + * package of the class. + * @see Constructor#newInstance(Object...) + * @see #invokeExactConstructor(Class, Object[], Class[]) + */ + public static T invokeExactConstructor(final Class cls, final Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + final Object[] actuals = ArrayUtils.nullToEmpty(args); + return invokeExactConstructor(cls, actuals, ClassUtils.toClass(actuals)); + } + + /** + * 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 construct. + * @param cls the class to construct, 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 Thrown if {@code cls} is {@code null}. + * @throws NoSuchMethodException Thrown if a matching constructor cannot be found. + * @throws IllegalAccessException Thrown if the found {@code Constructor} is enforcing Java language access control and the underlying constructor is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the number of actual and formal parameters differ; or
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value cannot be converted to the corresponding formal parameter type by a + * method invocation conversion; if this constructor pertains to an enum type. + *
+ * @throws InstantiationException Thrown if the class that declares the underlying constructor represents an abstract class. + * @throws InvocationTargetException Thrown if the underlying constructor throws an exception. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + * @throws SecurityException Thrown if a security manager is present and the caller's class loader is not the same as or an ancestor of the class + * loader for the class and invocation of {@link SecurityManager#checkPackageAccess(String)} denies access to the + * package of the class. + * @see Constructor#newInstance(Object...) + */ + public static T invokeExactConstructor(final Class cls, final Object[] args, final Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + final Constructor ctor = getAccessibleConstructor(cls, ArrayUtils.nullToEmpty(parameterTypes)); + if (ctor == null) { + throw new NoSuchMethodException("No such accessible constructor on object: " + cls.getName()); + } + return ctor.newInstance(ArrayUtils.nullToEmpty(args)); + } + + /** + * Tests 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}. + * @throws SecurityException Thrown if a security manager is present and a caller's class loader is not the same as or an ancestor of the class loader for a + * class and invocation of {@link SecurityManager#checkPackageAccess(String)} denies access to the package of the class. */ private static boolean isAccessible(final Class type) { Class cls = type; while (cls != null) { - if (!Modifier.isPublic(cls.getModifiers())) { + if (!ClassUtils.isPublic(cls)) { return false; } cls = cls.getEnclosingClass(); @@ -208,4 +324,18 @@ private static boolean isAccessible(final Class type) { return true; } -} + /** + * 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. + *

+ * + * @deprecated TODO Make private in 4.0. + */ + @Deprecated + public ConstructorUtils() { + // empty + } +} \ No newline at end of file diff --git a/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/MemberUtils.java b/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/MemberUtils.java index 0849b9d234..b30c2e4e3b 100644 --- a/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/MemberUtils.java +++ b/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/MemberUtils.java @@ -15,11 +15,10 @@ * limitations under the License. */ -/* + /* * This is a modified version of the original Apache class. It has had unused * members removed. */ - package org.ehcache.impl.internal.classes.commonslang.reflect; import org.ehcache.impl.internal.classes.commonslang.ClassUtils; @@ -31,67 +30,56 @@ import java.lang.reflect.Modifier; /** - * Contains common code for working with {@link Method Methods}/{@link Constructor Constructors}, + * 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 */ -abstract class MemberUtils { +final 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. + * A class providing a subset of the API of java.lang.reflect.Executable in Java 1.8, + * providing a common representation for function signatures for Constructors and Methods. */ - static boolean setAccessibleWorkaround(final AccessibleObject o) { - if (o == null || o.isAccessible()) { - return false; + private static final class Executable { + + private static Executable of(final Constructor constructor) { + return new Executable(constructor); } - 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 - } + + private static Executable of(final Method method) { + return new Executable(method); } - 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; - } + private final Class[] parameterTypes; + private final boolean isVarArgs; - /** - * 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(); + private Executable(final Constructor constructor) { + parameterTypes = constructor.getParameterTypes(); + isVarArgs = constructor.isVarArgs(); + } + + private Executable(final Method method) { + parameterTypes = method.getParameterTypes(); + isVarArgs = method.isVarArgs(); + } + + public Class[] getParameterTypes() { + return parameterTypes; + } + + public boolean isVarArgs() { + return isVarArgs; + } } + 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 }; + /** * Compares the relative fitness of two Constructors in terms of how well they * match a set of runtime parameter types, such that a list ordered @@ -106,10 +94,27 @@ static boolean isAccessible(final Member m) { * @since 3.5 */ static int compareConstructorFit(final Constructor left, final Constructor right, final Class[] actual) { - return compareParameterTypes(Executable.of(left), Executable.of(right), actual); + return compareParameterTypes(Executable.of(left), Executable.of(right), actual); } - /** + /** + * Compares the relative fitness of two Methods in terms of how well they + * match a 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" Method + * @param right the "right" Method + * @param actual the runtime parameter types to match against + * {@code left}/{@code right} + * @return int consistent with {@code compare} semantics + * @since 3.5 + */ + static int compareMethodFit(final Method left, final Method right, final Class[] actual) { + return compareParameterTypes(Executable.of(left), Executable.of(right), actual); + } + + /** * Compares the relative fitness of two Executables in terms of how well they * match a set of runtime parameter types, such that a list ordered * by the results of the comparison would return the best match first @@ -124,56 +129,11 @@ static int compareConstructorFit(final Constructor left, final Constructor private static int compareParameterTypes(final Executable left, final Executable right, final Class[] actual) { final float leftCost = getTotalTransformationCost(actual, left); final float rightCost = getTotalTransformationCost(actual, right); - return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0; + return Float.compare(leftCost, rightCost); } /** - * Returns the sum of the object transformation cost for each class in the - * source argument list. - * @param srcArgs The source arguments - * @param executable The executable to calculate transformation costs for - * @return The total transformation cost - */ - private static float getTotalTransformationCost(final Class[] srcArgs, final Executable executable) { - final Class[] destArgs = executable.getParameterTypes(); - final boolean isVarArgs = executable.isVarArgs(); - - // "source" and "destination" are the actual and declared args respectively. - float totalCost = 0.0f; - final long normalArgsLen = isVarArgs ? destArgs.length-1 : destArgs.length; - if (srcArgs.length < normalArgsLen) { - return Float.MAX_VALUE; - } - for (int i = 0; i < normalArgsLen; i++) { - totalCost += getObjectTransformationCost(srcArgs[i], destArgs[i]); - } - if (isVarArgs) { - // When isVarArgs is true, srcArgs and dstArgs may differ in length. - // There are two special cases to consider: - final boolean noVarArgsPassed = srcArgs.length < destArgs.length; - final boolean explicitArrayForVarags = srcArgs.length == destArgs.length && srcArgs[srcArgs.length-1].isArray(); - - final float varArgsCost = 0.001f; - final Class destClass = destArgs[destArgs.length-1].getComponentType(); - if (noVarArgsPassed) { - // When no varargs passed, the best match is the most generic matching type, not the most specific. - totalCost += getObjectTransformationCost(destClass, Object.class) + varArgsCost; - } else if (explicitArrayForVarags) { - final Class sourceClass = srcArgs[srcArgs.length-1].getComponentType(); - totalCost += getObjectTransformationCost(sourceClass, destClass) + varArgsCost; - } else { - // This is typical varargs case. - for (int i = destArgs.length-1; i < srcArgs.length; i++) { - final Class srcClass = srcArgs[i]; - totalCost += getObjectTransformationCost(srcClass, destClass) + varArgsCost; - } - } - } - return totalCost; - } - - /** - * Gets the number of steps required needed to turn the source class into + * Gets the number of steps 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 @@ -199,8 +159,7 @@ private static float getObjectTransformationCost(Class srcClass, final Class< srcClass = srcClass.getSuperclass(); } /* - * If the destination class is null, we've traveled all the way up to - * an Object match. We'll penalize this by adding 1.5 to the cost. + * If the destination class is null, we've traveled 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; @@ -216,6 +175,9 @@ private static float getObjectTransformationCost(Class srcClass, final Class< * @return The cost of promoting the primitive */ private static float getPrimitivePromotionCost(final Class srcClass, final Class destClass) { + if (srcClass == null) { + return 1.5f; + } float cost = 0.0f; Class cls = srcClass; if (!cls.isPrimitive()) { @@ -234,8 +196,64 @@ private static float getPrimitivePromotionCost(final Class srcClass, final Cl return cost; } + /** + * Gets the sum of the object transformation cost for each class in the + * source argument list. + * @param srcArgs The source arguments + * @param executable The executable to calculate transformation costs for + * @return The total transformation cost + */ + private static float getTotalTransformationCost(final Class[] srcArgs, final Executable executable) { + final Class[] destArgs = executable.getParameterTypes(); + final boolean isVarArgs = executable.isVarArgs(); + + // "source" and "destination" are the actual and declared args respectively. + float totalCost = 0.0f; + final long normalArgsLen = isVarArgs ? destArgs.length - 1 : destArgs.length; + if (srcArgs.length < normalArgsLen) { + return Float.MAX_VALUE; + } + for (int i = 0; i < normalArgsLen; i++) { + totalCost += getObjectTransformationCost(srcArgs[i], destArgs[i]); + } + if (isVarArgs) { + // When isVarArgs is true, srcArgs and dstArgs may differ in length. + // There are two special cases to consider: + final boolean noVarArgsPassed = srcArgs.length < destArgs.length; + final boolean explicitArrayForVarargs = srcArgs.length == destArgs.length && srcArgs[srcArgs.length - 1] != null + && srcArgs[srcArgs.length - 1].isArray(); + + final float varArgsCost = 0.001f; + final Class destClass = destArgs[destArgs.length - 1].getComponentType(); + if (noVarArgsPassed) { + // When no varargs passed, the best match is the most generic matching type, not the most specific. + totalCost += getObjectTransformationCost(destClass, Object.class) + varArgsCost; + } else if (explicitArrayForVarargs) { + final Class sourceClass = srcArgs[srcArgs.length - 1].getComponentType(); + totalCost += getObjectTransformationCost(sourceClass, destClass) + varArgsCost; + } else { + // This is typical varargs case. + for (int i = destArgs.length - 1; i < srcArgs.length; i++) { + final Class srcClass = srcArgs[i]; + totalCost += getObjectTransformationCost(srcClass, destClass) + varArgsCost; + } + } + } + return totalCost; + } + + /** + * Tests whether a {@link Member} is accessible. + * + * @param member Member to test, may be null. + * @return {@code true} if {@code m} is accessible + */ + static boolean isAccessible(final Member member) { + return isPublic(member) && !member.isSynthetic(); + } + static boolean isMatchingConstructor(final Constructor method, final Class[] parameterTypes) { - return MemberUtils.isMatchingExecutable(Executable.of(method), parameterTypes); + return isMatchingExecutable(Executable.of(method), parameterTypes); } private static boolean isMatchingExecutable(final Executable method, final Class[] parameterTypes) { @@ -243,7 +261,6 @@ private static boolean isMatchingExecutable(final Executable method, final Class if (ClassUtils.isAssignable(parameterTypes, methodParameterTypes, true)) { return true; } - if (method.isVarArgs()) { int i; for (i = 0; i < methodParameterTypes.length - 1 && i < parameterTypes.length; i++) { @@ -259,34 +276,75 @@ private static boolean isMatchingExecutable(final Executable method, final Class } return true; } - return false; } + static boolean isMatchingMethod(final Method method, final Class[] parameterTypes) { + return isMatchingExecutable(Executable.of(method), parameterTypes); + } + /** - *

A class providing a subset of the API of java.lang.reflect.Executable in Java 1.8, - * providing a common representation for function signatures for Constructors and Methods.

+ * Tests 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 */ - private static final class Executable { - private final Class[] parameterTypes; - private final boolean isVarArgs; + static boolean isPackageAccess(final int modifiers) { + return (modifiers & ACCESS_TEST) == 0; + } - private static Executable of(final Constructor constructor) { - return new Executable(constructor); - } + /** + * Tests whether a {@link Member} is public. + * + * @param member Member to test, may be null. + * @return {@code true} if {@code m} is public. + */ + static boolean isPublic(final Member member) { + return member != null && Modifier.isPublic(member.getModifiers()); + } - private Executable(final Constructor constructor) { - parameterTypes = constructor.getParameterTypes(); - isVarArgs = constructor.isVarArgs(); - } + /** + * Tests whether a {@link Member} is static. + * + * @param member Member to test, may be null. + * @return {@code true} if {@code m} is static. + */ + static boolean isStatic(final Member member) { + return member != null && Modifier.isStatic(member.getModifiers()); + } - public Class[] getParameterTypes() { - return parameterTypes; + /** + * 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 obj the AccessibleObject to set as accessible, may be null. + * @return a boolean indicating whether the accessibility of the object was set to true. + * @throws SecurityException if an underlying accessible object's method denies the request. + * @see SecurityManager#checkPermission + */ + @SuppressWarnings("deprecation") + static T setAccessibleWorkaround(final T obj) { + if (obj == null || obj.isAccessible()) { + return obj; } - - public boolean isVarArgs() { - return isVarArgs; + final Member m = (Member) obj; + if (!obj.isAccessible() && isPublic(m) && isPackageAccess(m.getDeclaringClass().getModifiers())) { + try { + obj.setAccessible(true); + return obj; + } catch (final SecurityException ignored) { + // ignore in favor of subsequent IllegalAccessException + } } + return obj; } -} +} \ No newline at end of file diff --git a/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/MethodUtils.java b/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/MethodUtils.java index 7ebe2cd8b1..0a9ef5ddfe 100644 --- a/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/MethodUtils.java +++ b/ehcache-impl/src/main/java/org/ehcache/impl/internal/classes/commonslang/reflect/MethodUtils.java @@ -24,72 +24,836 @@ import org.ehcache.impl.internal.classes.commonslang.ArrayUtils; import org.ehcache.impl.internal.classes.commonslang.ClassUtils; +import java.lang.annotation.Annotation; import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** - *

Utility reflection methods focused on {@link Method}s, originally from Commons BeanUtils. + * 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

+ *

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. + *

+ * {@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.

+ * If this call fails then the method may fail. + *

* * @since 2.5 */ public class MethodUtils { + private static final Comparator METHOD_BY_SIGNATURE = Comparator.comparing(Method::toString); + + /** + * Computes the aggregate number of inheritance hops between assignable argument class types. Returns -1 + * if the arguments aren't assignable. Fills a specific purpose for getMatchingMethod and is not generalized. + * + * @param fromClassArray the Class array to calculate the distance from. + * @param toClassArray the Class array to calculate the distance to. + * @return the aggregate number of inheritance hops between assignable argument class types. + */ + private static int distance(final Class[] fromClassArray, final Class[] toClassArray) { + int answer = 0; + if (!ClassUtils.isAssignable(fromClassArray, toClassArray, true)) { + return -1; + } + for (int offset = 0; offset < fromClassArray.length; offset++) { + // Note InheritanceUtils.distance() uses different scoring system. + final Class aClass = fromClassArray[offset]; + final Class toClass = toClassArray[offset]; + if (aClass == null || aClass.equals(toClass)) { + continue; + } + if (ClassUtils.isAssignable(aClass, toClass, true) && !ClassUtils.isAssignable(aClass, toClass, false)) { + answer++; + } else { + answer += 2; + } + } + return answer; + } + + /** + * Gets 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 cls The implementing class, may be null. + * @param method The method that we wish to call, may be null. + * @return The accessible method or null. + * @since 3.19.0 + */ + public static Method getAccessibleMethod(final Class cls, final Method method) { + if (!MemberUtils.isPublic(method)) { + return null; + } + // If the declaring class is public, we are done + if (ClassUtils.isPublic(cls)) { + return method; + } + final String methodName = method.getName(); + final Class[] parameterTypes = method.getParameterTypes(); + // Check the implemented interfaces and subinterfaces + final Method method2 = getAccessibleMethodFromInterfaceNest(cls, methodName, parameterTypes); + // Check the superclass chain + return method2 != null ? method2 : getAccessibleMethodFromSuperclass(cls, methodName, parameterTypes); + } + + /** + * Gets 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) { + return getAccessibleMethod(getMethodObject(cls, methodName, parameterTypes)); + } + + /** + * Gets 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, may be null. + * @return The accessible method + */ + public static Method getAccessibleMethod(final Method method) { + return method != null ? getAccessibleMethod(method.getDeclaringClass(), method) : null; + } + /** - *

Given an arguments array passed to a varargs method, return an array of arguments in the canonical form, - * i.e. an array with the declared number of parameters, and whose last parameter is an array of the varargs type. + * Gets 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 (final Class anInterface : interfaces) { + // Is this interface public? + if (!ClassUtils.isPublic(anInterface)) { + continue; + } + // Does the method exist on this interface? + try { + return anInterface.getDeclaredMethod(methodName, parameterTypes); + } catch (final NoSuchMethodException ignored) { + /* + * Swallow, if no method is found after the loop then this method returns null. + */ + } + // Recursively check our parent interfaces + final Method method = getAccessibleMethodFromInterfaceNest(anInterface, methodName, parameterTypes); + if (method != null) { + return method; + } + } + } + return null; + } + + /** + * Gets 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 (ClassUtils.isPublic(parentClass)) { + return getMethodObject(parentClass, methodName, parameterTypes); + } + parentClass = parentClass.getSuperclass(); + } + return null; + } + + /** + * Gets a combination of {@link ClassUtils#getAllSuperclasses(Class)} and + * {@link ClassUtils#getAllInterfaces(Class)}, one from superclasses, one + * from interfaces, and so on in a breadth first way. + * + * @param cls the class to look up, may be {@code null} + * @return the combined {@link List} of superclasses and interfaces in order + * going up from this one + * {@code null} if null input + */ + private static List> getAllSuperclassesAndInterfaces(final Class cls) { + if (cls == null) { + return null; + } + final List> allSuperClassesAndInterfaces = new ArrayList<>(); + final List> allSuperclasses = ClassUtils.getAllSuperclasses(cls); + int superClassIndex = 0; + final List> allInterfaces = ClassUtils.getAllInterfaces(cls); + int interfaceIndex = 0; + while (interfaceIndex < allInterfaces.size() || superClassIndex < allSuperclasses.size()) { + final Class acls; + if (interfaceIndex >= allInterfaces.size() || superClassIndex < allSuperclasses.size() && superClassIndex < interfaceIndex) { + acls = allSuperclasses.get(superClassIndex++); + } else { + acls = allInterfaces.get(interfaceIndex++); + } + allSuperClassesAndInterfaces.add(acls); + } + return allSuperClassesAndInterfaces; + } + + /** + * Gets 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 {@link 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 or null. + * @throws SecurityException if an underlying accessible object's method denies the request. + * @see SecurityManager#checkPermission + */ + public static Method getMatchingAccessibleMethod(final Class cls, final String methodName, final Class... parameterTypes) { + final Method candidate = getMethodObject(cls, methodName, parameterTypes); + if (candidate != null) { + return MemberUtils.setAccessibleWorkaround(candidate); + } + // search through all methods + final Method[] methods = cls.getMethods(); + final List matchingMethods = Stream.of(methods) + .filter(method -> method.getName().equals(methodName) && MemberUtils.isMatchingMethod(method, parameterTypes)).collect(Collectors.toList()); + // Sort methods by signature to force deterministic result + matchingMethods.sort(METHOD_BY_SIGNATURE); + Method bestMatch = null; + for (final Method method : matchingMethods) { + // get accessible version of method + final Method accessibleMethod = getAccessibleMethod(method); + if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareMethodFit(accessibleMethod, bestMatch, parameterTypes) < 0)) { + bestMatch = accessibleMethod; + } + } + if (bestMatch != null) { + MemberUtils.setAccessibleWorkaround(bestMatch); + } + if (bestMatch != null && bestMatch.isVarArgs() && bestMatch.getParameterTypes().length > 0 && parameterTypes.length > 0) { + final Class[] methodParameterTypes = bestMatch.getParameterTypes(); + final Class methodParameterComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType(); + final String methodParameterComponentTypeName = ClassUtils.primitiveToWrapper(methodParameterComponentType).getName(); + final Class lastParameterType = parameterTypes[parameterTypes.length - 1]; + final String parameterTypeName = lastParameterType == null ? null : lastParameterType.getName(); + final String parameterTypeSuperClassName = lastParameterType == null ? null + : lastParameterType.getSuperclass() != null ? lastParameterType.getSuperclass().getName() : null; + if (parameterTypeName != null && parameterTypeSuperClassName != null && !methodParameterComponentTypeName.equals(parameterTypeName) + && !methodParameterComponentTypeName.equals(parameterTypeSuperClassName)) { + return null; + } + } + return bestMatch; + } + + /** + * Gets a Method, or {@code null} if a documented {@link Class#getMethod(String, Class...) } exception is thrown. + * + * @param cls Receiver for {@link Class#getMethod(String, Class...)}. + * @param name the name of the method. + * @param parameterTypes the list of parameters. + * @return a Method or {@code null}. + * @see SecurityManager#checkPermission + * @see Class#getMethod(String, Class...) + * @since 3.15.0 + */ + public static Method getMethodObject(final Class cls, final String name, final Class... parameterTypes) { + try { + return name != null && cls != null ? cls.getMethod(name, parameterTypes) : null; + } catch (final NoSuchMethodException | SecurityException e) { + return null; + } + } + + /** + * Gets all class level public 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 NullPointerException + * if the class or annotation are {@code null} + * @since 3.4 + */ + public static List getMethodsListWithAnnotation(final Class cls, final Class annotationCls) { + return getMethodsListWithAnnotation(cls, annotationCls, false, false); + } + + /** + * 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 + * @param searchSupers + * determines if a lookup in the entire inheritance hierarchy of the given class should be performed + * @param ignoreAccess + * determines if non-public methods should be considered + * @return a list of Methods (possibly empty). + * @throws NullPointerException if either the class or annotation class is {@code null} + * @since 3.6 + */ + public static List getMethodsListWithAnnotation(final Class cls, final Class annotationCls, final boolean searchSupers, + final boolean ignoreAccess) { + Objects.requireNonNull(cls, "cls"); + Objects.requireNonNull(annotationCls, "annotationCls"); + final List> classes = searchSupers ? getAllSuperclassesAndInterfaces(cls) : new ArrayList<>(); + classes.add(0, cls); + final List annotatedMethods = new ArrayList<>(); + classes.forEach(acls -> { + final Method[] methods = ignoreAccess ? acls.getDeclaredMethods() : acls.getMethods(); + Stream.of(methods).filter(method -> method.isAnnotationPresent(annotationCls)).forEachOrdered(annotatedMethods::add); + }); + return annotatedMethods; + } + + /** + * Gets all class level public 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 NullPointerException if the class or annotation are {@code null} + * @since 3.4 + */ + public static Method[] getMethodsWithAnnotation(final Class cls, final Class annotationCls) { + return getMethodsWithAnnotation(cls, annotationCls, false, false); + } + + /** + * 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 + * @param searchSupers + * determines if a lookup in the entire inheritance hierarchy of the given class should be performed + * @param ignoreAccess + * determines if non-public methods should be considered + * @return an array of Methods (possibly empty). + * @throws NullPointerException if the class or annotation are {@code null} + * @since 3.6 + */ + public static Method[] getMethodsWithAnnotation(final Class cls, final Class annotationCls, final boolean searchSupers, + final boolean ignoreAccess) { + return getMethodsListWithAnnotation(cls, annotationCls, searchSupers, ignoreAccess).toArray(ArrayUtils.EMPTY_METHOD_ARRAY); + } + + /** + * Gets an array of arguments in the canonical form, given an arguments array passed to a varargs method, + * for example an array with the declared number of parameters, and whose last parameter is an array of the varargs type. + * * @param args the array of arguments passed to the varags method * @param methodParameterTypes the declared array of method parameter types * @return an array of the variadic arguments passed to the method * @since 3.5 */ static Object[] getVarArgs(final Object[] args, final Class[] methodParameterTypes) { - if (args.length == methodParameterTypes.length - && args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1])) { - // The args array is already in the canonical form for the method. - return args; + final int mptLength = methodParameterTypes.length; + if (args.length == mptLength) { + final Object lastArg = args[args.length - 1]; + if (lastArg == null || lastArg.getClass().equals(methodParameterTypes[mptLength - 1])) { + // The args array is already in the canonical form for the method. + return args; + } } - // Construct a new array matching the method's declared parameter types. - final Object[] newArgs = new Object[methodParameterTypes.length]; - // Copy the normal (non-varargs) parameters - System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1); - + final Object[] newArgs = ArrayUtils.arraycopy(args, 0, 0, mptLength - 1, () -> new Object[mptLength]); // Construct a new array for the variadic parameters - final Class varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType(); - final int varArgLength = args.length - methodParameterTypes.length + 1; - - Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength); + final Class varArgComponentType = methodParameterTypes[mptLength - 1].getComponentType(); + final int varArgLength = args.length - mptLength + 1; // Copy the variadic arguments into the varargs array. - System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength); - - if(varArgComponentType.isPrimitive()) { + Object varArgsArray = ArrayUtils.arraycopy(args, mptLength - 1, 0, varArgLength, + s -> Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength)); + if (varArgComponentType.isPrimitive()) { // unbox from wrapper type to primitive type varArgsArray = ArrayUtils.toPrimitive(varArgsArray); } - // Store the varargs array in the last position of the array to return - newArgs[methodParameterTypes.length - 1] = varArgsArray; - + newArgs[mptLength - 1] = varArgsArray; // Return the canonical varargs array. return newArgs; } -} + /** + * Invokes a method whose parameter types match exactly the object type. + * + *

+ * 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 Thrown if there is no such accessible method. + * @throws IllegalAccessException Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of + * the class or interface declaring the underlying method (or of a subclass or interface implementor);
  • + *
  • the number of actual and formal parameters differ;
  • + *
+ * @throws InvocationTargetException Thrown if the underlying method throws an exception. + * @throws NullPointerException Thrown if the specified {@code object} is null. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + * @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 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. + * @param args use these arguments - treat null as empty array. + * @return The value returned by the invoked method. + * @throws NoSuchMethodException Thrown if there is no such accessible method. + * @throws IllegalAccessException Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of + * the class or interface declaring the underlying method (or of a subclass or interface implementor);
  • + *
  • the number of actual and formal parameters differ;
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a + * method invocation conversion.
  • + *
+ * @throws InvocationTargetException Thrown if the underlying method throws an exception. + * @throws NullPointerException Thrown if the specified {@code object} is null. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + */ + public static Object invokeExactMethod(final Object object, final String methodName, final Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + final Object[] actuals = ArrayUtils.nullToEmpty(args); + return invokeExactMethod(object, methodName, actuals, ClassUtils.toClass(actuals)); + } + + /** + * 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 Invokes a method on this object. + * @param methodName Gets a method with this name. + * @param args Method 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 Thrown if there is no such accessible method. + * @throws IllegalAccessException Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of + * the class or interface declaring the underlying method (or of a subclass or interface implementor);
  • + *
  • the number of actual and formal parameters differ;
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a + * method invocation conversion.
  • + *
+ * @throws InvocationTargetException Thrown if the underlying method throws an exception. + * @throws NullPointerException Thrown if the specified {@code object} is null. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + */ + public static Object invokeExactMethod(final Object object, final String methodName, final Object[] args, final Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + final Class cls = Objects.requireNonNull(object, "object").getClass(); + final Method method = getAccessibleMethod(cls, methodName, ArrayUtils.nullToEmpty(parameterTypes)); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + methodName + "() on object: " + cls.getName()); + } + return method.invoke(object, ArrayUtils.nullToEmpty(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 Thrown if there is no such accessible method. + * @throws IllegalAccessException Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of + * the class or interface declaring the underlying method (or of a subclass or interface implementor);
  • + *
  • the number of actual and formal parameters differ;
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a + * method invocation conversion.
  • + *
+ * @throws InvocationTargetException Thrown if the underlying method throws an exception. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + */ + public static Object invokeExactStaticMethod(final Class cls, final String methodName, final Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + final Object[] actuals = ArrayUtils.nullToEmpty(args); + return invokeExactStaticMethod(cls, methodName, actuals, ClassUtils.toClass(actuals)); + } + + /** + * 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 Thrown if there is no such accessible method. + * @throws IllegalAccessException Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of + * the class or interface declaring the underlying method (or of a subclass or interface implementor);
  • + *
  • the number of actual and formal parameters differ;
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a + * method invocation conversion.
  • + *
+ * @throws InvocationTargetException Thrown if the underlying method throws an exception. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + */ + public static Object invokeExactStaticMethod(final Class cls, final String methodName, final Object[] args, final Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + final Method method = getAccessibleMethod(cls, methodName, ArrayUtils.nullToEmpty(parameterTypes)); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + methodName + "() on class: " + cls.getName()); + } + return method.invoke(null, ArrayUtils.nullToEmpty(args)); + } + + /** + * Invokes a named method without parameters. + * + *

+ * This is a convenient wrapper for + * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + * @param object invoke method on this object + * @param forceAccess force access to invoke method even if it's not accessible + * @param methodName get method with this name + * @return The value returned by the invoked method + * @throws NoSuchMethodException Thrown if there is no such accessible method. + * @throws IllegalAccessException Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of + * the class or interface declaring the underlying method (or of a subclass or interface implementor);
  • + *
  • the number of actual and formal parameters differ;
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a + * method invocation conversion.
  • + *
+ * @throws InvocationTargetException Thrown if the underlying method throws an exception. + * @throws NullPointerException Thrown if the specified {@code object} is null. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + * @see SecurityManager#checkPermission + * @since 3.5 + */ + public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return invokeMethod(object, forceAccess, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); + } + + /** + * Invokes a named method whose parameter type matches the object type. + * + *

+ * This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@link Boolean} object + * would match a {@code boolean} primitive. + *

+ *

+ * This is a convenient wrapper for + * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + * @param object invoke method on this object + * @param forceAccess force access to invoke method even if it's not accessible + * @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 Thrown if there is no such accessible method. + * @throws IllegalAccessException Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of + * the class or interface declaring the underlying method (or of a subclass or interface implementor);
  • + *
  • the number of actual and formal parameters differ;
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a + * method invocation conversion.
  • + *
+ * @throws InvocationTargetException Thrown if the underlying method throws an exception. + * @throws NullPointerException Thrown if the specified {@code object} is null. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + * @see SecurityManager#checkPermission + * @since 3.5 + */ + public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName, final Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + final Object[] actuals = ArrayUtils.nullToEmpty(args); + return invokeMethod(object, forceAccess, methodName, actuals, ClassUtils.toClass(actuals)); + } + + /** + * 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 + * @throws SecurityException if an underlying accessible object's method denies the request. + * @see SecurityManager#checkPermission + * @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 {@link 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 + * @throws NullPointerException if the object or method name are {@code null} + * @throws SecurityException if an underlying accessible object's method denies the request. + * @see SecurityManager#checkPermission + */ + public static Object invokeMethod(final Object object, final String methodName, final Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + final Object[] actuals = ArrayUtils.nullToEmpty(args); + return invokeMethod(object, methodName, actuals, ClassUtils.toClass(actuals)); + } + + /** + * 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 {@link 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 Thrown if there is no such accessible method. + * @throws IllegalAccessException Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of + * the class or interface declaring the underlying method (or of a subclass or interface implementor);
  • + *
  • the number of actual and formal parameters differ;
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a + * method invocation conversion.
  • + *
+ * @throws InvocationTargetException Thrown if the underlying method throws an exception. + * @throws NullPointerException Thrown if the specified {@code object} is null. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + * @see SecurityManager#checkPermission + */ + public static Object invokeMethod(final Object object, final String methodName, final Object[] args, final Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return invokeMethod(object, false, 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 {@link 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 Thrown if there is no such accessible method. + * @throws IllegalAccessException Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of + * the class or interface declaring the underlying method (or of a subclass or interface implementor);
  • + *
  • the number of actual and formal parameters differ;
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a + * method invocation conversion.
  • + *
+ * @throws InvocationTargetException Thrown if the underlying method throws an exception. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + * @see SecurityManager#checkPermission + */ + public static Object invokeStaticMethod(final Class cls, final String methodName, final Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + final Object[] actuals = ArrayUtils.nullToEmpty(args); + return invokeStaticMethod(cls, methodName, actuals, ClassUtils.toClass(actuals)); + } + + /** + * 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 {@link 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 Thrown if there is no such accessible method. + * @throws IllegalAccessException Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is + * inaccessible. + * @throws IllegalArgumentException Thrown if: + *
    + *
  • the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of + * the class or interface declaring the underlying method (or of a subclass or interface implementor);
  • + *
  • the number of actual and formal parameters differ;
  • + *
  • an unwrapping conversion for primitive arguments fails; or
  • + *
  • after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a + * method invocation conversion.
  • + *
+ * @throws InvocationTargetException Thrown if the underlying method throws an exception. + * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails. + * @see SecurityManager#checkPermission + */ + public static Object invokeStaticMethod(final Class cls, final String methodName, final Object[] args, final Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + final Method method = getMatchingAccessibleMethod(cls, methodName, ArrayUtils.nullToEmpty(parameterTypes)); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + methodName + "() on class: " + cls.getName()); + } + return method.invoke(null, toVarArgs(method, ArrayUtils.nullToEmpty(args))); + } + + private static Object[] toVarArgs(final Method method, final Object[] args) { + return method.isVarArgs() ? getVarArgs(args, method.getParameterTypes()) : args; + } +} \ No newline at end of file diff --git a/ehcache-impl/src/main/java/org/ehcache/impl/serialization/PlainJavaSerializer.java b/ehcache-impl/src/main/java/org/ehcache/impl/serialization/PlainJavaSerializer.java index 54d60a9e3a..c5ebfdeaa7 100644 --- a/ehcache-impl/src/main/java/org/ehcache/impl/serialization/PlainJavaSerializer.java +++ b/ehcache-impl/src/main/java/org/ehcache/impl/serialization/PlainJavaSerializer.java @@ -101,12 +101,14 @@ protected Class resolveClass(ObjectStreamClass desc) throws ClassNotFoundExce } @Override + @SuppressWarnings("deprecation") protected Class resolveProxyClass(String[] interfaces) throws ClassNotFoundException { Class[] interfaceClasses = new Class[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { interfaceClasses[i] = Class.forName(interfaces[i], false, classLoader); } + // WARNING: Proxy classes generated in a named module with deprecated getProxyClass() are encapsulated and not accessible to code outside its module. Constructor.newInstance will throw IllegalAccessException when it is called on an inaccessible proxy class. return Proxy.getProxyClass(classLoader, interfaceClasses); } diff --git a/ehcache-impl/src/main/resources/META-INF/services/org.ehcache.core.spi.service.ServiceFactory b/ehcache-impl/src/main/resources/META-INF/services/org.ehcache.core.spi.service.ServiceFactory index 82e5621139..4c47439759 100644 --- a/ehcache-impl/src/main/resources/META-INF/services/org.ehcache.core.spi.service.ServiceFactory +++ b/ehcache-impl/src/main/resources/META-INF/services/org.ehcache.core.spi.service.ServiceFactory @@ -11,7 +11,6 @@ org.ehcache.impl.internal.store.shared.authoritative.SharedAuthoritativeTierProv org.ehcache.impl.internal.store.shared.caching.SharedCachingTierProviderFactory org.ehcache.impl.internal.store.shared.caching.lower.SharedLowerCachingTierProviderFactory org.ehcache.impl.internal.store.shared.caching.higher.SharedHigherCachingTierProviderFactory - org.ehcache.impl.internal.TimeSourceServiceFactory org.ehcache.impl.internal.spi.serialization.DefaultSerializationProviderFactory org.ehcache.impl.internal.spi.loaderwriter.DefaultCacheLoaderWriterProviderFactory diff --git a/ehcache-impl/src/test/java/org/ehcache/config/builders/CacheConfigurationBuilderTest.java b/ehcache-impl/src/test/java/org/ehcache/config/builders/CacheConfigurationBuilderTest.java index 3402a4a863..607d997e06 100644 --- a/ehcache-impl/src/test/java/org/ehcache/config/builders/CacheConfigurationBuilderTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/config/builders/CacheConfigurationBuilderTest.java @@ -391,8 +391,10 @@ public void testIncompatibleServiceRemovesExistingConfiguration() { CacheConfigurationBuilder newBuilder = oldBuilder.withService(newConfig); - assertThat(oldBuilder.build().getServiceConfigurations(), both(hasItem(sameInstance(oldConfig))).and(not(hasItem(sameInstance(newConfig))))); - assertThat(newBuilder.build().getServiceConfigurations(), both(hasItem(sameInstance(newConfig))).and(not(hasItem(sameInstance(oldConfig))))); + assertThat(oldBuilder.build().getServiceConfigurations(), hasItem(sameInstance(oldConfig))); + assertThat(oldBuilder.build().getServiceConfigurations(), not(hasItem(sameInstance(newConfig)))); + assertThat(newBuilder.build().getServiceConfigurations(), hasItem(sameInstance(newConfig))); + assertThat(newBuilder.build().getServiceConfigurations(), not(hasItem(sameInstance(oldConfig)))); } @Test @@ -400,12 +402,14 @@ public void testCompatibleServiceJoinsExistingConfiguration() { ServiceConfiguration oldConfig = new CompatibleServiceConfig(); ServiceConfiguration newConfig = new CompatibleServiceConfig(); - CacheConfigurationBuilder oldBuilder = newCacheConfigurationBuilder(Object.class, Object.class, heap(10)).withService(oldConfig); + CacheConfigurationBuilder oldBuilder = newCacheConfigurationBuilder(Object.class, Object.class, heap(10)).withService(oldConfig); - CacheConfigurationBuilder newBuilder = oldBuilder.withService(newConfig); + CacheConfigurationBuilder newBuilder = oldBuilder.withService(newConfig); - assertThat(oldBuilder.build().getServiceConfigurations(), both(hasItem(sameInstance(oldConfig))).and(not(hasItem(sameInstance(newConfig))))); - assertThat(newBuilder.build().getServiceConfigurations(), both(hasItem(sameInstance(oldConfig))).and(hasItem(sameInstance(newConfig)))); + assertThat(oldBuilder.build().getServiceConfigurations(), hasItem(sameInstance(oldConfig))); + assertThat(oldBuilder.build().getServiceConfigurations(), not(hasItem(sameInstance(newConfig)))); + assertThat(newBuilder.build().getServiceConfigurations(), hasItem(sameInstance(oldConfig))); + assertThat(newBuilder.build().getServiceConfigurations(), hasItem(sameInstance(newConfig))); } @Test diff --git a/ehcache-impl/src/test/java/org/ehcache/docs/ConfigurationDerivation.java b/ehcache-impl/src/test/java/org/ehcache/docs/ConfigurationDerivation.java index 6b38682668..bcfaaff5d7 100644 --- a/ehcache-impl/src/test/java/org/ehcache/docs/ConfigurationDerivation.java +++ b/ehcache-impl/src/test/java/org/ehcache/docs/ConfigurationDerivation.java @@ -207,7 +207,7 @@ public OptimizedDateSerializer(ClassLoader classLoader) {} @Override public ByteBuffer serialize(Date object) throws SerializerException { ByteBuffer buffer = ByteBuffer.allocate(8); - return (ByteBuffer) buffer.putLong(object.getTime()).flip(); + return buffer.putLong(object.getTime()).flip(); } @Override diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/internal/store/offheap/AssertingOffHeapValueHolder.java b/ehcache-impl/src/test/java/org/ehcache/impl/internal/store/offheap/AssertingOffHeapValueHolder.java index ba74892607..8ebc1f03ee 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/internal/store/offheap/AssertingOffHeapValueHolder.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/internal/store/offheap/AssertingOffHeapValueHolder.java @@ -41,7 +41,7 @@ import static java.util.Arrays.stream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.objectweb.asm.Opcodes.ASM6; +import static org.objectweb.asm.Opcodes.ASM7; import static org.objectweb.asm.Type.getObjectType; import static org.objectweb.asm.Type.getType; import static org.objectweb.asm.commons.Method.getMethod; @@ -136,11 +136,11 @@ private static boolean isLockedInFrame(StackTraceElement ste) { NavigableMap lockLevels = new TreeMap<>(); - reader.accept(new ClassVisitor(ASM6) { + reader.accept(new ClassVisitor(ASM7) { @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { if (ste.getMethodName().equals(name)) { - return new InstructionAdapter(ASM6, new MethodVisitor(ASM6) {}) { + return new InstructionAdapter(ASM7, new MethodVisitor(ASM7) {}) { private final Map levels = new HashMap<>(); diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/AddedFieldTest.java b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/AddedFieldTest.java index 31612486f1..488513668e 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/AddedFieldTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/AddedFieldTest.java @@ -47,7 +47,7 @@ public void addingSerializableField() throws Exception { serializer.init(new TransientStateRepository()); ClassLoader loaderA = createClassNameRewritingLoader(A_write.class, IncompatibleSerializable_write.class, Serializable_write.class); - Serializable a = (Serializable) loaderA.loadClass(newClassName(A_write.class)).newInstance(); + Serializable a = (Serializable) loaderA.loadClass(newClassName(A_write.class)).getDeclaredConstructor().newInstance(); ByteBuffer encodedA = serializer.serialize(a); pushTccl(createClassNameRewritingLoader(A_read.class, IncompatibleSerializable_read.class)); @@ -65,7 +65,7 @@ public void addingExternalizableField() throws Exception { serializer.init(new TransientStateRepository()); ClassLoader loaderA = createClassNameRewritingLoader(B_write.class, Externalizable_write.class); - Serializable a = (Serializable) loaderA.loadClass(newClassName(B_write.class)).newInstance(); + Serializable a = (Serializable) loaderA.loadClass(newClassName(B_write.class)).getDeclaredConstructor().newInstance(); ByteBuffer encodedA = serializer.serialize(a); pushTccl(createClassNameRewritingLoader(B_read.class)); diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/AddedSuperClassTest.java b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/AddedSuperClassTest.java index 6e897bab53..ca4d245ddb 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/AddedSuperClassTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/AddedSuperClassTest.java @@ -41,7 +41,7 @@ public void testAddedSuperClass() throws Exception { serializer.init(new TransientStateRepository()); ClassLoader loaderA = createClassNameRewritingLoader(A_2.class, AddedSuperClass_Hidden.class); - Serializable a = (Serializable) loaderA.loadClass(newClassName(A_2.class)).newInstance(); + Serializable a = (Serializable) loaderA.loadClass(newClassName(A_2.class)).getDeclaredConstructor().newInstance(); ByteBuffer encodedA = serializer.serialize(a); pushTccl(createClassNameRewritingLoader(A_1.class)); @@ -58,7 +58,7 @@ public void testAddedSuperClassNotHidden() throws Exception { serializer.init(new TransientStateRepository()); ClassLoader loaderA = createClassNameRewritingLoader(A_2.class, AddedSuperClass_Hidden.class); - Serializable a = (Serializable) loaderA.loadClass(newClassName(A_2.class)).newInstance(); + Serializable a = (Serializable) loaderA.loadClass(newClassName(A_2.class)).getDeclaredConstructor().newInstance(); ByteBuffer encodedA = serializer.serialize(a); pushTccl(createClassNameRewritingLoader(A_1.class, AddedSuperClass_Hidden.class)); diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/CompactJavaSerializerClassLoaderTest.java b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/CompactJavaSerializerClassLoaderTest.java index a7d9bb25bd..c01a28e532 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/CompactJavaSerializerClassLoaderTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/CompactJavaSerializerClassLoaderTest.java @@ -42,7 +42,7 @@ public void testThreadContextLoader() throws Exception { serializer.init(new TransientStateRepository()); ClassLoader loader = newLoader(); - ByteBuffer encoded = serializer.serialize((Serializable) loader.loadClass(Foo.class.getName()).newInstance()); + ByteBuffer encoded = serializer.serialize((Serializable) loader.loadClass(Foo.class.getName()).getDeclaredConstructor().newInstance()); pushTccl(loader); try { @@ -58,7 +58,7 @@ public void testExplicitLoader() throws Exception { StatefulSerializer serializer = new CompactJavaSerializer<>(loader); serializer.init(new TransientStateRepository()); - ByteBuffer encoded = serializer.serialize((Serializable) loader.loadClass(Foo.class.getName()).newInstance()); + ByteBuffer encoded = serializer.serialize((Serializable) loader.loadClass(Foo.class.getName()).getDeclaredConstructor().newInstance()); // setting TCCL doesn't matter here, but set it to make sure it doesn't get used pushTccl(newLoader()); diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/CompactJavaSerializerClassUnloadingTest.java b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/CompactJavaSerializerClassUnloadingTest.java index b115b68eb7..15aab1730d 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/CompactJavaSerializerClassUnloadingTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/CompactJavaSerializerClassUnloadingTest.java @@ -49,7 +49,7 @@ public void createSpecialObject() throws Exception { Class special = (Class) duplicate.loadClass(SpecialClass.class.getName()); classRef = new WeakReference<>(special); - specialObject = special.newInstance(); + specialObject = special.getDeclaredConstructor().newInstance(); } @Test diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/EnumTest.java b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/EnumTest.java index c7bdf1087d..6a647ee9ff 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/EnumTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/EnumTest.java @@ -20,6 +20,7 @@ import org.ehcache.core.spi.store.TransientStateRepository; import org.ehcache.spi.serialization.StatefulSerializer; import org.junit.Test; +import org.junit.Ignore; import java.io.Serializable; @@ -57,7 +58,7 @@ public void classSerialization() throws ClassNotFoundException { assertThat(s.read(s.serialize(Dogs.Penny.getClass())), sameInstance(Dogs.Penny.getClass())); } - @Test + @Test @Ignore("change in access rules for 17 cause this to fail") public void shiftingInstanceSerialization() throws ClassNotFoundException { StatefulSerializer s = new CompactJavaSerializer<>(null); s.init(new TransientStateRepository()); diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/GetFieldTest.java b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/GetFieldTest.java index ccb69e6f9f..3926168ae6 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/GetFieldTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/GetFieldTest.java @@ -39,12 +39,11 @@ public class GetFieldTest { @Test public void testGetField() throws Exception { - @SuppressWarnings("unchecked") StatefulSerializer s = new CompactJavaSerializer<>(null); s.init(new TransientStateRepository()); ClassLoader loaderA = createClassNameRewritingLoader(Foo_A.class); - Serializable a = (Serializable) loaderA.loadClass(newClassName(Foo_A.class)).newInstance(); + Serializable a = (Serializable) loaderA.loadClass(newClassName(Foo_A.class)).getDeclaredConstructor().newInstance(); ByteBuffer encodedA = s.serialize(a); pushTccl(createClassNameRewritingLoader(Foo_B.class)); diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/JavaSerializer.java b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/JavaSerializer.java index ec1774fe36..cf8b3b9db4 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/JavaSerializer.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/JavaSerializer.java @@ -108,12 +108,14 @@ protected Class resolveClass(ObjectStreamClass desc) throws IOException, Clas } @Override + @SuppressWarnings("deprecation") protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { Class[] interfaceClasses = new Class[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { interfaceClasses[i] = Class.forName(interfaces[i], false, classLoader); } + // WARNING: Proxy classes generated in a named module with deprecated getProxyClass() are encapsulated and not accessible to code outside its module. Constructor.newInstance will throw IllegalAccessException when it is called on an inaccessible proxy class. return Proxy.getProxyClass(classLoader, interfaceClasses); } diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/PutFieldTest.java b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/PutFieldTest.java index bc2da6c360..5122085349 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/PutFieldTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/PutFieldTest.java @@ -45,7 +45,7 @@ public void testWithAllPrimitivesAndString() throws Exception { s.init(new TransientStateRepository()); ClassLoader loaderA = createClassNameRewritingLoader(Foo_A.class); - Serializable a = (Serializable) loaderA.loadClass(newClassName(Foo_A.class)).newInstance(); + Serializable a = (Serializable) loaderA.loadClass(newClassName(Foo_A.class)).getDeclaredConstructor().newInstance(); ByteBuffer encodedA = s.serialize(a); pushTccl(Foo.class.getClassLoader()); @@ -72,7 +72,7 @@ public void testWithTwoStrings() throws Exception { s.init(new TransientStateRepository()); ClassLoader loaderA = createClassNameRewritingLoader(Bar_A.class); - Serializable a = (Serializable) loaderA.loadClass(newClassName(Bar_A.class)).newInstance(); + Serializable a = (Serializable) loaderA.loadClass(newClassName(Bar_A.class)).getDeclaredConstructor().newInstance(); ByteBuffer encodedA = s.serialize(a); pushTccl(Bar.class.getClassLoader()); diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/ReadObjectNoDataTest.java b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/ReadObjectNoDataTest.java index 7c9bc2ca01..5a30f20906 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/ReadObjectNoDataTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/ReadObjectNoDataTest.java @@ -44,7 +44,7 @@ public void test() throws Exception { ClassLoader loaderW = createClassNameRewritingLoader(C_W.class, B_W.class); - ByteBuffer b = s.serialize((Serializable) loaderW.loadClass(newClassName(C_W.class)).newInstance()); + ByteBuffer b = s.serialize((Serializable) loaderW.loadClass(newClassName(C_W.class)).getDeclaredConstructor().newInstance()); pushTccl(createClassNameRewritingLoader(C_R.class, B_R.class, A_R.class)); try { diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/SerializeAfterEvolutionTest.java b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/SerializeAfterEvolutionTest.java index 80246ae1a7..bafd4cfb54 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/serialization/SerializeAfterEvolutionTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/serialization/SerializeAfterEvolutionTest.java @@ -36,12 +36,11 @@ public class SerializeAfterEvolutionTest { @Test public void test() throws Exception { - @SuppressWarnings("unchecked") StatefulSerializer s = new CompactJavaSerializer<>(null); s.init(new TransientStateRepository()); ClassLoader loaderA = createClassNameRewritingLoader(A_old.class); - Serializable a = (Serializable) loaderA.loadClass(newClassName(A_old.class)).newInstance(); + Serializable a = (Serializable) loaderA.loadClass(newClassName(A_old.class)).getDeclaredConstructor().newInstance(); ByteBuffer encodedA = s.serialize(a); ClassLoader loaderB = createClassNameRewritingLoader(A_new.class); @@ -50,7 +49,7 @@ public void test() throws Exception { Serializable outA = s.read(encodedA); assertThat((Integer) outA.getClass().getField("integer").get(outA), is(42)); - Serializable b = (Serializable) loaderB.loadClass(newClassName(A_new.class)).newInstance(); + Serializable b = (Serializable) loaderB.loadClass(newClassName(A_new.class)).getDeclaredConstructor().newInstance(); Serializable outB = s.read(s.serialize(b)); assertThat((Integer) outB.getClass().getField("integer").get(outB), is(42)); } finally { diff --git a/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMap.java b/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMap.java index 97ad217521..f1a16121e2 100644 --- a/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMap.java +++ b/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMap.java @@ -2583,7 +2583,6 @@ else if (f instanceof TreeBin) { * A padded cell for distributing counts. Adapted from LongAdder * and Striped64. See their internal docs for explanation. */ - @sun.misc.Contended static final class CounterCell { volatile long value; CounterCell(long x) { value = x; } diff --git a/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ThreadLocalRandomUtil.java b/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ThreadLocalRandomUtil.java index 2ac8242a4f..3f66b98c7b 100644 --- a/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ThreadLocalRandomUtil.java +++ b/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ThreadLocalRandomUtil.java @@ -48,6 +48,7 @@ class ThreadLocalRandomUtil { } } + @SuppressWarnings("removal") static Unsafe getSMU() { try { return sun.misc.Unsafe.getUnsafe(); diff --git a/ehcache-management/build.gradle b/ehcache-management/build.gradle index 055ec39134..39e37d0d73 100644 --- a/ehcache-management/build.gradle +++ b/ehcache-management/build.gradle @@ -36,14 +36,14 @@ dependencies { // optional: if we want to use the clustered management layer compileOnly project(':clustered:ehcache-client') - compileOnly "org.terracotta:entity-client-api:$terracottaApisVersion" - compileOnlyApi ("org.terracotta.management:nms-agent-entity-client:$terracottaPlatformVersion") { + compileOnly "org.terracotta:client-api:$terracottaApisVersion" + compileOnlyApi ("org.terracotta:terracotta-management-entities-nms-agent-client:$terracottaPlatformVersion") { // This is to avoid stats lib being directly used. exclude group:'org.terracotta', module:'statistics' } - testImplementation "org.terracotta.management:management-registry:$terracottaPlatformVersion" + testImplementation "org.terracotta:terracotta-management-registry:$terracottaPlatformVersion" testImplementation project(':ehcache-xml') - testImplementation "org.terracotta.common:common-json-support:$terracottaPlatformVersion" + testImplementation "org.terracotta:terracotta-json:$terracottaPlatformVersion" testImplementation testFixtures(project(':ehcache-xml')) } diff --git a/ehcache-management/src/test/java/org/ehcache/management/registry/DefaultManagementRegistryServiceTest.java b/ehcache-management/src/test/java/org/ehcache/management/registry/DefaultManagementRegistryServiceTest.java index 2b4f6ebd95..6f4b5b774f 100644 --- a/ehcache-management/src/test/java/org/ehcache/management/registry/DefaultManagementRegistryServiceTest.java +++ b/ehcache-management/src/test/java/org/ehcache/management/registry/DefaultManagementRegistryServiceTest.java @@ -120,7 +120,7 @@ public void descriptorOnHeapTest() { allDescriptors.addAll(ONHEAP_DESCRIPTORS); allDescriptors.addAll(CACHE_DESCRIPTORS); - assertThat(descriptors).hasSameElementsAs(allDescriptors); + assertThat(allDescriptors).hasSameElementsAs(descriptors); } } @@ -150,7 +150,7 @@ public void descriptorOnHeapTest_withoutStats() { allDescriptors.addAll(ONHEAP_NO_STATS_DESCRIPTORS); allDescriptors.addAll(CACHE_DESCRIPTORS); - assertThat(descriptors).hasSameElementsAs(allDescriptors); + assertThat(allDescriptors).hasSameElementsAs(descriptors); } } @@ -181,7 +181,7 @@ public void descriptorOffHeapTest() { allDescriptors.addAll(OFFHEAP_DESCRIPTORS); allDescriptors.addAll(CACHE_DESCRIPTORS); - assertThat(descriptors).hasSameElementsAs(allDescriptors); + assertThat(allDescriptors).hasSameElementsAs(descriptors); } } @@ -215,7 +215,7 @@ public void descriptorDiskStoreTest() throws Exception { allDescriptors.addAll(DISK_DESCRIPTORS); allDescriptors.addAll(CACHE_DESCRIPTORS); - assertThat(descriptors).hasSameElementsAs(allDescriptors); + assertThat(allDescriptors).hasSameElementsAs(descriptors); } } diff --git a/ehcache-transactions/src/common/java/org/ehcache/transactions/xa/internal/SoftLockSerializer.java b/ehcache-transactions/src/common/java/org/ehcache/transactions/xa/internal/SoftLockSerializer.java index 44b9cbac75..585fe4e3ef 100644 --- a/ehcache-transactions/src/common/java/org/ehcache/transactions/xa/internal/SoftLockSerializer.java +++ b/ehcache-transactions/src/common/java/org/ehcache/transactions/xa/internal/SoftLockSerializer.java @@ -111,12 +111,14 @@ protected Class resolveClass(ObjectStreamClass desc) throws ClassNotFoundExce } @Override + @SuppressWarnings("deprecation") protected Class resolveProxyClass(String[] interfaces) throws ClassNotFoundException { Class[] interfaceClasses = new Class[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { interfaceClasses[i] = Class.forName(interfaces[i], false, classLoader); } + // WARNING: Proxy classes generated in a named module with deprecated getProxyClass() are encapsulated and not accessible to code outside its module. Constructor.newInstance will throw IllegalAccessException when it is called on an inaccessible proxy class. return Proxy.getProxyClass(classLoader, interfaceClasses); } diff --git a/ehcache-transactions/src/jakarta/java/org/ehcache/transactions/xa/txmgr/provider/LookupTransactionManagerProvider.java b/ehcache-transactions/src/jakarta/java/org/ehcache/transactions/xa/txmgr/provider/LookupTransactionManagerProvider.java index 97f78f02f9..e02c6ecef6 100644 --- a/ehcache-transactions/src/jakarta/java/org/ehcache/transactions/xa/txmgr/provider/LookupTransactionManagerProvider.java +++ b/ehcache-transactions/src/jakarta/java/org/ehcache/transactions/xa/txmgr/provider/LookupTransactionManagerProvider.java @@ -18,6 +18,7 @@ package org.ehcache.transactions.xa.txmgr.provider; import jakarta.transaction.TransactionManager; +import java.lang.reflect.InvocationTargetException; import org.ehcache.spi.service.Service; import org.ehcache.spi.service.ServiceProvider; import org.ehcache.transactions.xa.txmgr.TransactionManagerWrapper; @@ -55,8 +56,8 @@ public LookupTransactionManagerProvider(LookupTransactionManagerProviderConfigur throw new NullPointerException("LookupTransactionManagerProviderConfiguration cannot be null"); } try { - lookup = config.getTransactionManagerLookup().newInstance(); - } catch (InstantiationException | IllegalAccessException e) { + lookup = config.getTransactionManagerLookup().getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new IllegalArgumentException("Could not instantiate lookup class", e); } } diff --git a/ehcache-transactions/src/main/java/org/ehcache/transactions/xa/txmgr/provider/LookupTransactionManagerProvider.java b/ehcache-transactions/src/main/java/org/ehcache/transactions/xa/txmgr/provider/LookupTransactionManagerProvider.java index b8ea5a2764..586f9337cb 100644 --- a/ehcache-transactions/src/main/java/org/ehcache/transactions/xa/txmgr/provider/LookupTransactionManagerProvider.java +++ b/ehcache-transactions/src/main/java/org/ehcache/transactions/xa/txmgr/provider/LookupTransactionManagerProvider.java @@ -20,7 +20,7 @@ import org.ehcache.spi.service.Service; import org.ehcache.spi.service.ServiceProvider; import org.ehcache.transactions.xa.txmgr.TransactionManagerWrapper; - +import java.lang.reflect.InvocationTargetException; import javax.transaction.TransactionManager; /** @@ -56,8 +56,8 @@ public LookupTransactionManagerProvider(LookupTransactionManagerProviderConfigur throw new NullPointerException("LookupTransactionManagerProviderConfiguration cannot be null"); } try { - lookup = config.getTransactionManagerLookup().newInstance(); - } catch (InstantiationException | IllegalAccessException e) { + lookup = config.getTransactionManagerLookup().getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new IllegalArgumentException("Could not instantiate lookup class", e); } } diff --git a/ehcache-transactions/src/test/java/org/ehcache/transactions/xa/utils/JavaSerializer.java b/ehcache-transactions/src/test/java/org/ehcache/transactions/xa/utils/JavaSerializer.java index dab8615e97..d823d8860a 100644 --- a/ehcache-transactions/src/test/java/org/ehcache/transactions/xa/utils/JavaSerializer.java +++ b/ehcache-transactions/src/test/java/org/ehcache/transactions/xa/utils/JavaSerializer.java @@ -108,12 +108,14 @@ protected Class resolveClass(ObjectStreamClass desc) throws IOException, Clas } @Override + @SuppressWarnings("deprecation") protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { Class[] interfaceClasses = new Class[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { interfaceClasses[i] = Class.forName(interfaces[i], false, classLoader); } + // WARNING: Proxy classes generated in a named module with deprecated getProxyClass() are encapsulated and not accessible to code outside its module. Constructor.newInstance will throw IllegalAccessException when it is called on an inaccessible proxy class. return Proxy.getProxyClass(classLoader, interfaceClasses); } diff --git a/ehcache-xml/build.gradle b/ehcache-xml/build.gradle index c9dcd8e286..64ac2910a8 100644 --- a/ehcache-xml/build.gradle +++ b/ehcache-xml/build.gradle @@ -115,6 +115,7 @@ dependencies { xjcToolJakarta 'com.sun.xml.bind:jaxb-xjc:3.0.0-M4' xjcToolJakarta 'com.sun.xml.bind:jaxb-impl:3.0.0-M4' + xjcToolJakarta 'javax.xml.bind:jaxb-api:[2.2,3)' lowerBoundTestRuntimeClasspath 'com.sun.activation:javax.activation:1.2.0' } diff --git a/ehcache-xml/ehcache-xml-spi/src/main/java/org/ehcache/xml/ParsingUtil.java b/ehcache-xml/ehcache-xml-spi/src/main/java/org/ehcache/xml/ParsingUtil.java index 073f74d274..6a9019174b 100644 --- a/ehcache-xml/ehcache-xml-spi/src/main/java/org/ehcache/xml/ParsingUtil.java +++ b/ehcache-xml/ehcache-xml-spi/src/main/java/org/ehcache/xml/ParsingUtil.java @@ -24,19 +24,18 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static java.security.AccessController.doPrivileged; - @PublicApi public class ParsingUtil { private static final Pattern SYSPROP = Pattern.compile("\\$\\{(?[^{}]+)}"); private static final Pattern PADDED_SYSPROP = Pattern.compile("\\s*" + SYSPROP.pattern() + "\\s*"); + @SuppressWarnings("removal") public static String parsePropertyOrString(String s) { Matcher matcher = PADDED_SYSPROP.matcher(s); if (matcher.matches()) { String property = matcher.group("property"); - String value = doPrivileged((PrivilegedAction) () -> System.getProperty(property)); + String value = java.security.AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(property)); if (value == null) { throw new IllegalStateException(String.format("Replacement for ${%s} not found!", property)); } else { @@ -69,12 +68,13 @@ public static BigInteger parsePropertyOrNonNegativeInteger(String s) { } } + @SuppressWarnings("removal") public static String parseStringWithProperties(String s) { Matcher matcher = SYSPROP.matcher(s); StringBuffer sb = new StringBuffer(); while (matcher.find()) { final String property = matcher.group("property"); - final String value = doPrivileged((PrivilegedAction) () -> System.getProperty(property)); + final String value = java.security.AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(property)); if (value == null) { throw new IllegalStateException(String.format("Replacement for ${%s} not found!", property)); } diff --git a/ehcache-xml/src/jakarta/java/org/ehcache/xml/ConfigurationParser.java b/ehcache-xml/src/jakarta/java/org/ehcache/xml/ConfigurationParser.java index 3b19d87feb..afcbc410c9 100644 --- a/ehcache-xml/src/jakarta/java/org/ehcache/xml/ConfigurationParser.java +++ b/ehcache-xml/src/jakarta/java/org/ehcache/xml/ConfigurationParser.java @@ -150,7 +150,7 @@ private static Stream stream(Iterable iterable) { CacheConfigurationBuilder parseServiceConfigurations(Document document, CacheConfigurationBuilder cacheBuilder, ClassLoader cacheClassLoader, CacheTemplate cacheDefinition) - throws ClassNotFoundException, IllegalAccessException, InstantiationException { + throws ReflectiveOperationException { cacheBuilder = CORE_CACHE_CONFIGURATION_PARSER.parse(cacheDefinition, cacheClassLoader, cacheBuilder); return serviceConfigurationParser.parse(document, cacheDefinition, cacheClassLoader, cacheBuilder); } @@ -194,7 +194,7 @@ private Map getTemplates(Document document, C private XmlConfiguration.Template parseTemplate(Document document, CacheTemplate template) { return new XmlConfiguration.Template() { @Override - public CacheConfigurationBuilder builderFor(ClassLoader classLoader, Class keyType, Class valueType, ResourcePools resources) throws ClassNotFoundException, InstantiationException, IllegalAccessException { + public CacheConfigurationBuilder builderFor(ClassLoader classLoader, Class keyType, Class valueType, ResourcePools resources) throws ReflectiveOperationException { checkTemplateTypeConsistency("key", classLoader, keyType, template); checkTemplateTypeConsistency("value", classLoader, valueType, template); @@ -228,7 +228,7 @@ public Document uriToDocument(URI uri) throws IOException, SAXException { return documentBuilder.parse(uri.toString()); } - public XmlConfigurationWrapper documentToConfig(Document document, ClassLoader classLoader, Map cacheClassLoaders) throws JAXBException, ClassNotFoundException, InstantiationException, IllegalAccessException { + public XmlConfigurationWrapper documentToConfig(Document document, ClassLoader classLoader, Map cacheClassLoaders) throws JAXBException, ReflectiveOperationException { Document annotatedDocument = stampExternalConfigurations(copyAndValidate(document)); Element root = annotatedDocument.getDocumentElement(); diff --git a/ehcache-xml/src/jakarta/java/org/ehcache/xml/CoreCacheConfigurationParser.java b/ehcache-xml/src/jakarta/java/org/ehcache/xml/CoreCacheConfigurationParser.java index 02666ca854..dd16f71ddd 100644 --- a/ehcache-xml/src/jakarta/java/org/ehcache/xml/CoreCacheConfigurationParser.java +++ b/ehcache-xml/src/jakarta/java/org/ehcache/xml/CoreCacheConfigurationParser.java @@ -30,7 +30,6 @@ import org.ehcache.xml.model.ExpiryType; import org.ehcache.xml.model.ObjectFactory; import org.ehcache.xml.model.TimeTypeWithPropSubst; - import java.math.BigInteger; import java.time.Duration; import java.util.stream.Stream; @@ -44,7 +43,7 @@ public class CoreCacheConfigurationParser { public CacheConfigurationBuilder parse(CacheTemplate cacheDefinition, ClassLoader cacheClassLoader, - CacheConfigurationBuilder cacheBuilder) throws ClassNotFoundException, IllegalAccessException, InstantiationException { + CacheConfigurationBuilder cacheBuilder) throws ReflectiveOperationException { final Expiry parsedExpiry = cacheDefinition.expiry(); if (parsedExpiry != null) { cacheBuilder = cacheBuilder.withExpiry(getExpiry(cacheClassLoader, parsedExpiry)); @@ -59,7 +58,7 @@ public CacheConfigurationBuilder parse(CacheTemplate cacheDefinitio @SuppressWarnings({"unchecked", "deprecation"}) private static ExpiryPolicy getExpiry(ClassLoader cacheClassLoader, Expiry parsedExpiry) - throws ClassNotFoundException, InstantiationException, IllegalAccessException { + throws ReflectiveOperationException { if (parsedExpiry.isUserDef()) { try { return getInstanceOfName(parsedExpiry.type(), cacheClassLoader, ExpiryPolicy.class); @@ -75,12 +74,12 @@ public CacheConfigurationBuilder parse(CacheTemplate cacheDefinitio } } - static T getInstanceOfName(String name, ClassLoader classLoader, Class type) throws ClassNotFoundException, InstantiationException, IllegalAccessException { + static T getInstanceOfName(String name, ClassLoader classLoader, Class type) throws ReflectiveOperationException { if (name == null) { return null; } Class klazz = getClassForName(name, classLoader); - return klazz.asSubclass(type).newInstance(); + return klazz.asSubclass(type).getDeclaredConstructor().newInstance(); } public CacheType unparse(CacheConfiguration cacheConfiguration, CacheType cacheType) { diff --git a/ehcache-xml/src/jakarta/java/org/ehcache/xml/XmlConfiguration.java b/ehcache-xml/src/jakarta/java/org/ehcache/xml/XmlConfiguration.java index 7db144bfb0..5447f80c81 100644 --- a/ehcache-xml/src/jakarta/java/org/ehcache/xml/XmlConfiguration.java +++ b/ehcache-xml/src/jakarta/java/org/ehcache/xml/XmlConfiguration.java @@ -264,15 +264,14 @@ public URL getURL() { * * @throws IllegalStateException if the template does not configure resources. * @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the declared type(s) of the template - * @throws ClassNotFoundException if a {@link Class} declared in the XML couldn't be found - * @throws InstantiationException if a user provided {@link Class} couldn't get instantiated - * @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a user provided type + * @throws ReflectiveOperationException if a {@link Class} declared in the XML couldn't be found, + * or if a user provided {@link Class} couldn't get instantiated, + * or if a method (including constructor) couldn't be invoked on a user provided type */ - @SuppressWarnings("unchecked") public CacheConfigurationBuilder newCacheConfigurationBuilderFromTemplate(final String name, final Class keyType, final Class valueType) - throws InstantiationException, IllegalAccessException, ClassNotFoundException { + throws ReflectiveOperationException { Template template = templates.get(name); if (template == null) { return null; @@ -298,16 +297,15 @@ public CacheConfigurationBuilder newCacheConfigurationBuilderFromTe * or {@code null} if no cache-template for the provided {@code name} * * @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the declared type(s) of the template - * @throws ClassNotFoundException if a {@link Class} declared in the XML couldn't be found - * @throws InstantiationException if a user provided {@link Class} couldn't get instantiated - * @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a user provided type + * @throws ReflectiveOperationException if a {@link Class} declared in the XML couldn't be found, + * or if a user provided {@link Class} couldn't get instantiated, + * or if a method (including constructor) couldn't be invoked on a user provided type */ - @SuppressWarnings("unchecked") public CacheConfigurationBuilder newCacheConfigurationBuilderFromTemplate(final String name, final Class keyType, final Class valueType, final ResourcePools resourcePools) - throws InstantiationException, IllegalAccessException, ClassNotFoundException { + throws ReflectiveOperationException { Template template = templates.get(name); if (template == null) { return null; @@ -333,16 +331,15 @@ public CacheConfigurationBuilder newCacheConfigurationBuilderFromTe * or {@code null} if no cache-template for the provided {@code name} * * @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the declared type(s) of the template - * @throws ClassNotFoundException if a {@link Class} declared in the XML couldn't be found - * @throws InstantiationException if a user provided {@link Class} couldn't get instantiated - * @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a user provided type + * @throws ReflectiveOperationException if a {@link Class} declared in the XML couldn't be found, + * or if a user provided {@link Class} couldn't get instantiated, + * or if a method (including constructor) couldn't be invoked on a user provided type */ - @SuppressWarnings("unchecked") public CacheConfigurationBuilder newCacheConfigurationBuilderFromTemplate(final String name, final Class keyType, final Class valueType, final Builder resourcePoolsBuilder) - throws InstantiationException, IllegalAccessException, ClassNotFoundException { + throws ReflectiveOperationException { return newCacheConfigurationBuilderFromTemplate(name, keyType, valueType, resourcePoolsBuilder.build()); } @@ -372,7 +369,7 @@ public FluentConfigurationBuilder derive() { } public interface Template { - CacheConfigurationBuilder builderFor(ClassLoader classLoader, Class keyType, Class valueType, ResourcePools resourcePools) throws ClassNotFoundException, InstantiationException, IllegalAccessException; + CacheConfigurationBuilder builderFor(ClassLoader classLoader, Class keyType, Class valueType, ResourcePools resourcePools) throws ReflectiveOperationException; } public static Class getClassForName(String name, ClassLoader classLoader) throws ClassNotFoundException { @@ -403,11 +400,11 @@ public static Class getClassForName(String name, ClassLoader classLoader) thr try { return forName(n, false, l); } catch (ClassNotFoundException e) { - int innerSeperator = n.lastIndexOf("."); - if (innerSeperator == -1) { + int innerSeparator = n.lastIndexOf("."); + if (innerSeparator == -1) { throw e; } else { - return forName(n.substring(0, innerSeperator) + "$" + n.substring(innerSeperator + 1), false, l); + return forName(n.substring(0, innerSeparator) + "$" + n.substring(innerSeparator + 1), false, l); } } }), diff --git a/ehcache-xml/src/main/java/org/ehcache/xml/ConfigurationParser.java b/ehcache-xml/src/main/java/org/ehcache/xml/ConfigurationParser.java index 275b7d35cc..0f525243d1 100644 --- a/ehcache-xml/src/main/java/org/ehcache/xml/ConfigurationParser.java +++ b/ehcache-xml/src/main/java/org/ehcache/xml/ConfigurationParser.java @@ -149,7 +149,7 @@ private static Stream stream(Iterable iterable) { CacheConfigurationBuilder parseServiceConfigurations(Document document, CacheConfigurationBuilder cacheBuilder, ClassLoader cacheClassLoader, CacheTemplate cacheDefinition) - throws ClassNotFoundException, IllegalAccessException, InstantiationException { + throws ReflectiveOperationException { cacheBuilder = CORE_CACHE_CONFIGURATION_PARSER.parse(cacheDefinition, cacheClassLoader, cacheBuilder); return serviceConfigurationParser.parse(document, cacheDefinition, cacheClassLoader, cacheBuilder); } @@ -193,7 +193,7 @@ private Map getTemplates(Document document, C private XmlConfiguration.Template parseTemplate(Document document, CacheTemplate template) { return new XmlConfiguration.Template() { @Override - public CacheConfigurationBuilder builderFor(ClassLoader classLoader, Class keyType, Class valueType, ResourcePools resources) throws ClassNotFoundException, InstantiationException, IllegalAccessException { + public CacheConfigurationBuilder builderFor(ClassLoader classLoader, Class keyType, Class valueType, ResourcePools resources) throws ReflectiveOperationException { checkTemplateTypeConsistency("key", classLoader, keyType, template); checkTemplateTypeConsistency("value", classLoader, valueType, template); @@ -227,7 +227,7 @@ public Document uriToDocument(URI uri) throws IOException, SAXException { return documentBuilder.parse(uri.toString()); } - public XmlConfigurationWrapper documentToConfig(Document document, ClassLoader classLoader, Map cacheClassLoaders) throws JAXBException, ClassNotFoundException, InstantiationException, IllegalAccessException { + public XmlConfigurationWrapper documentToConfig(Document document, ClassLoader classLoader, Map cacheClassLoaders) throws JAXBException, ReflectiveOperationException { Document annotatedDocument = stampExternalConfigurations(copyAndValidate(document)); Element root = annotatedDocument.getDocumentElement(); diff --git a/ehcache-xml/src/main/java/org/ehcache/xml/CoreCacheConfigurationParser.java b/ehcache-xml/src/main/java/org/ehcache/xml/CoreCacheConfigurationParser.java index 02666ca854..dd16f71ddd 100644 --- a/ehcache-xml/src/main/java/org/ehcache/xml/CoreCacheConfigurationParser.java +++ b/ehcache-xml/src/main/java/org/ehcache/xml/CoreCacheConfigurationParser.java @@ -30,7 +30,6 @@ import org.ehcache.xml.model.ExpiryType; import org.ehcache.xml.model.ObjectFactory; import org.ehcache.xml.model.TimeTypeWithPropSubst; - import java.math.BigInteger; import java.time.Duration; import java.util.stream.Stream; @@ -44,7 +43,7 @@ public class CoreCacheConfigurationParser { public CacheConfigurationBuilder parse(CacheTemplate cacheDefinition, ClassLoader cacheClassLoader, - CacheConfigurationBuilder cacheBuilder) throws ClassNotFoundException, IllegalAccessException, InstantiationException { + CacheConfigurationBuilder cacheBuilder) throws ReflectiveOperationException { final Expiry parsedExpiry = cacheDefinition.expiry(); if (parsedExpiry != null) { cacheBuilder = cacheBuilder.withExpiry(getExpiry(cacheClassLoader, parsedExpiry)); @@ -59,7 +58,7 @@ public CacheConfigurationBuilder parse(CacheTemplate cacheDefinitio @SuppressWarnings({"unchecked", "deprecation"}) private static ExpiryPolicy getExpiry(ClassLoader cacheClassLoader, Expiry parsedExpiry) - throws ClassNotFoundException, InstantiationException, IllegalAccessException { + throws ReflectiveOperationException { if (parsedExpiry.isUserDef()) { try { return getInstanceOfName(parsedExpiry.type(), cacheClassLoader, ExpiryPolicy.class); @@ -75,12 +74,12 @@ public CacheConfigurationBuilder parse(CacheTemplate cacheDefinitio } } - static T getInstanceOfName(String name, ClassLoader classLoader, Class type) throws ClassNotFoundException, InstantiationException, IllegalAccessException { + static T getInstanceOfName(String name, ClassLoader classLoader, Class type) throws ReflectiveOperationException { if (name == null) { return null; } Class klazz = getClassForName(name, classLoader); - return klazz.asSubclass(type).newInstance(); + return klazz.asSubclass(type).getDeclaredConstructor().newInstance(); } public CacheType unparse(CacheConfiguration cacheConfiguration, CacheType cacheType) { diff --git a/ehcache-xml/src/main/java/org/ehcache/xml/XmlConfiguration.java b/ehcache-xml/src/main/java/org/ehcache/xml/XmlConfiguration.java index 3363470e2a..9df830b390 100644 --- a/ehcache-xml/src/main/java/org/ehcache/xml/XmlConfiguration.java +++ b/ehcache-xml/src/main/java/org/ehcache/xml/XmlConfiguration.java @@ -264,14 +264,14 @@ public URL getURL() { * * @throws IllegalStateException if the template does not configure resources. * @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the declared type(s) of the template - * @throws ClassNotFoundException if a {@link java.lang.Class} declared in the XML couldn't be found - * @throws InstantiationException if a user provided {@link java.lang.Class} couldn't get instantiated - * @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a user provided type + * @throws ReflectiveOperationException if a {@link java.lang.Class} declared in the XML couldn't be found, + * or if a user provided {@link java.lang.Class} couldn't get instantiated, + * or if a method (including constructor) couldn't be invoked on a user provided type */ public CacheConfigurationBuilder newCacheConfigurationBuilderFromTemplate(final String name, final Class keyType, final Class valueType) - throws InstantiationException, IllegalAccessException, ClassNotFoundException { + throws ReflectiveOperationException { Template template = templates.get(name); if (template == null) { return null; @@ -297,15 +297,15 @@ public CacheConfigurationBuilder newCacheConfigurationBuilderFromTe * or {@code null} if no cache-template for the provided {@code name} * * @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the declared type(s) of the template - * @throws ClassNotFoundException if a {@link java.lang.Class} declared in the XML couldn't be found - * @throws InstantiationException if a user provided {@link java.lang.Class} couldn't get instantiated - * @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a user provided type + * @throws ReflectiveOperationException if a {@link java.lang.Class} declared in the XML couldn't be found, + * or if a user provided {@link java.lang.Class} couldn't get instantiated, + * or if a method (including constructor) couldn't be invoked on a user provided type */ public CacheConfigurationBuilder newCacheConfigurationBuilderFromTemplate(final String name, final Class keyType, final Class valueType, final ResourcePools resourcePools) - throws InstantiationException, IllegalAccessException, ClassNotFoundException { + throws ReflectiveOperationException { Template template = templates.get(name); if (template == null) { return null; @@ -331,15 +331,15 @@ public CacheConfigurationBuilder newCacheConfigurationBuilderFromTe * or {@code null} if no cache-template for the provided {@code name} * * @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the declared type(s) of the template - * @throws ClassNotFoundException if a {@link java.lang.Class} declared in the XML couldn't be found - * @throws InstantiationException if a user provided {@link java.lang.Class} couldn't get instantiated - * @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a user provided type + * @throws ReflectiveOperationException if a {@link java.lang.Class} declared in the XML couldn't be found, + * or if a user provided {@link java.lang.Class} couldn't get instantiated, + * or if a method (including constructor) couldn't be invoked on a user provided type */ public CacheConfigurationBuilder newCacheConfigurationBuilderFromTemplate(final String name, final Class keyType, final Class valueType, final Builder resourcePoolsBuilder) - throws InstantiationException, IllegalAccessException, ClassNotFoundException { + throws ReflectiveOperationException { return newCacheConfigurationBuilderFromTemplate(name, keyType, valueType, resourcePoolsBuilder.build()); } @@ -369,7 +369,7 @@ public FluentConfigurationBuilder derive() { } public interface Template { - CacheConfigurationBuilder builderFor(ClassLoader classLoader, Class keyType, Class valueType, ResourcePools resourcePools) throws ClassNotFoundException, InstantiationException, IllegalAccessException; + CacheConfigurationBuilder builderFor(ClassLoader classLoader, Class keyType, Class valueType, ResourcePools resourcePools) throws ReflectiveOperationException; } public static Class getClassForName(String name, ClassLoader classLoader) throws ClassNotFoundException { diff --git a/ehcache-xml/src/test/java/org/ehcache/xml/IntegrationConfigurationTest.java b/ehcache-xml/src/test/java/org/ehcache/xml/IntegrationConfigurationTest.java index e33f0eb713..3266ac2161 100644 --- a/ehcache-xml/src/test/java/org/ehcache/xml/IntegrationConfigurationTest.java +++ b/ehcache-xml/src/test/java/org/ehcache/xml/IntegrationConfigurationTest.java @@ -125,7 +125,7 @@ public void testLoaderWriter() throws ClassNotFoundException, SAXException, Inst final Cache cache = cacheManager.getCache("bar", Number.class, String.class); assertThat(cache, notNullValue()); assertThat(cache.get(1), notNullValue()); - final Number key = new Long(42); + final Number key = 42L; cache.put(key, "Bye y'all!"); assertThat(TestCacheLoaderWriter.lastWrittenKey, is(key)); @@ -133,7 +133,7 @@ public void testLoaderWriter() throws ClassNotFoundException, SAXException, Inst final Cache templateCache = cacheManager.getCache("template1", Number.class, String.class); assertThat(templateCache, notNullValue()); assertThat(templateCache.get(1), notNullValue()); - final Number key1 = new Long(100); + final Number key1 = 100L; templateCache.put(key1, "Bye y'all!"); assertThat(TestCacheLoaderWriter.lastWrittenKey, is(key1)); } diff --git a/ehcache/build.gradle b/ehcache/build.gradle index 98cc84e224..1dfba7def1 100644 --- a/ehcache/build.gradle +++ b/ehcache/build.gradle @@ -69,6 +69,8 @@ configurations { jakartaContents { exclude group:'jakarta.xml.bind' } + javadocAdd + jakartaJavadocAdd } dependencies { @@ -94,6 +96,13 @@ dependencies { } } jakartaRuntimeOnly 'org.glassfish.jaxb:jaxb-runtime:[3,3.1)' + + javadocAdd 'com.github.spotbugs:spotbugs-annotations:4.2.3' + javadocAdd 'javax.xml.bind:jaxb-api:[2.2,3)' + + jakartaJavadocAdd 'com.github.spotbugs:spotbugs-annotations:4.2.3' + jakartaJavadocAdd 'jakarta.xml.bind:jakarta.xml.bind-api:[3,4)' + } tasks.named('jakartaJar') { @@ -103,7 +112,7 @@ tasks.named('jakartaJar') { instruction Constants.BUNDLE_DESCRIPTION, 'Ehcache is an open-source caching library, compliant with the JSR-107 standard.' instruction Constants.BUNDLE_ACTIVATOR, 'org.ehcache.core.osgi.EhcacheActivator' instruction Constants.EXPORT_PACKAGE, '!org.ehcache.jsr107.tck, !org.ehcache.*.internal.*, org.ehcache.*' - instruction Constants.IMPORT_PACKAGE, 'javax.cache.*;resolution:=optional, jdk.internal.misc;resolution:=optional, !javax.annotation, !sun.misc, jakarta.xml.bind*;version="[3,4)", *' + instruction Constants.IMPORT_PACKAGE, 'javax.annotation.*;resolution:=optional, javax.cache.*;resolution:=optional, jdk.internal.misc;resolution:=optional, !javax.annotation, !sun.misc, jakarta.xml.bind*;version="[3,4)", *' } } @@ -114,6 +123,16 @@ tasks.named('jar') { instruction Constants.BUNDLE_DESCRIPTION, 'Ehcache is an open-source caching library, compliant with the JSR-107 standard.' instruction Constants.BUNDLE_ACTIVATOR, 'org.ehcache.core.osgi.EhcacheActivator' instruction Constants.EXPORT_PACKAGE, '!org.ehcache.jsr107.tck, !org.ehcache.*.internal.*, org.ehcache.*' - instruction Constants.IMPORT_PACKAGE, 'javax.cache.*;resolution:=optional, jdk.internal.misc;resolution:=optional, !javax.annotation, !sun.misc, javax.xml.bind*;version="[2.2,3)", *' + instruction Constants.IMPORT_PACKAGE, 'javax.annotation.*;resolution:=optional, javax.cache.*;resolution:=optional, jdk.internal.misc;resolution:=optional, !javax.annotation, !sun.misc, javax.xml.bind*;version="[2.2,3)", *' } } + +tasks.named('javadoc') { + classpath += configurations.javadocAdd +} + +tasks.named('jakartaJavadoc') { + classpath += configurations.jakartaJavadocAdd +} + + diff --git a/gradle.properties b/gradle.properties index ba94751176..c0c19a1b1a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Ehcache version -ehcacheVersion = 3.11-SNAPSHOT +ehcacheVersion = 3.12-SNAPSHOT # Terracotta third parties offheapVersion = 2.5.6 @@ -9,10 +9,9 @@ slf4jVersion = 1.7.36 sizeofVersion = 0.4.4 # Terracotta clustered -terracottaPlatformVersion = 5.10.22 -terracottaApisVersion = 1.9.1 -terracottaCoreVersion = 5.10.15 -terracottaPassthroughTestingVersion = 1.9.1 +terracottaPlatformVersion = 5.11.1 +terracottaApisVersion = 5.12.+ +terracottaRuntimeVersion = 5.12.14 terracottaUtilitiesVersion = 0.0.19 # Test lib versions diff --git a/integration-test/src/test/java/org/ehcache/integration/OverSizeMappingTest.java b/integration-test/src/test/java/org/ehcache/integration/OverSizeMappingTest.java index e23b6f9f8e..29d7d37877 100644 --- a/integration-test/src/test/java/org/ehcache/integration/OverSizeMappingTest.java +++ b/integration-test/src/test/java/org/ehcache/integration/OverSizeMappingTest.java @@ -124,7 +124,7 @@ private static class ObjectSizeGreaterThanN implements Serializable { private ObjectSizeGreaterThanN(int n) { arr = new Integer[n]; for (int i = 0; i < arr.length; i++) { - arr[i] = new Integer(i); + arr[i] = i; } }