11

I have a method params is a list which is lager than 50000 items; Limited to the business logic, the list must less than 30000, so that I have a method to split this array to 2d array before the logic

public static final <T> Collection<List<T>> partitionBasedOnSize(List<T> inputList, int size) {
        AtomicInteger counter = new AtomicInteger(0);
        return inputList.stream().collect(Collectors.groupingBy(s -> counter.getAndIncrement() / size)).values();
}

This is my current solution:

public List<Account> getChildrenList(List<Long> ids) {
        List<Account> childrenList = new ArrayList<>();
        Collection<List<Long>> childrenId2dList = PartitionArray.partitionBasedOnSize(childrenIdsList, 30000);
        for (List<Long> list : childrenId2dList) {
            //this is my business logic: start
            childrenList.addAll(accountRepository.getAccounts(list));
            //this is my business logic: end
        }
        return childrenAccountsList;
}

I would like to create an annotation on top of the method instead of many duplicate code(check and spite each time...)

Sorry for my bad english, I have draw a diagram: method called>spite array>business logic>collect all result>return enter image description here

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
Ben Luk
  • 735
  • 2
  • 8
  • 15

2 Answers2

2

In my opinion using annotations is a bit of over-engineering (You have to write an annotation processor) in this case. You could easily use generics and lambdas and/or method references to reach your goal. For instance:

Update PartitionArray this way:

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

public class PartitionArray {

    private static <T> Collection<List<T>> partitionBasedOnSize(List<T> inputList, int partitionSize) {
        Collection<List<T>> collection = new ArrayList<>();
        int remainingSize = inputList.size();
        int index = 0;
        while (remainingSize > partitionSize) {
            collection.add(inputList.subList(index, index + partitionSize));
            remainingSize -= partitionSize;
            index += partitionSize;
        }
        collection.add(inputList.subList(index, index + remainingSize));
        return collection;
    }

    public static <D, T> List<D> partitionAndDoBusinessFunction(List<T> ids, Function<List<T>, List<D>> businessFunction, int partitionSize) {
        List<D> dataList = new ArrayList<>();
        Collection<List<T>> idListCollection = partitionBasedOnSize(ids, partitionSize);
        for (List<T> idList : idListCollection) {
            dataList.addAll(businessFunction.apply(idList));
        }
        return dataList;
    }
}

Then simply use it from your AccountService (using method reference):

import java.util.List;

public class AccountService {

    private AccountRepository accountRepository;

    public List<Account> getAccounts(List<Long> ids) {
        return PartitionArray.partitionAndDoBusinessFunction(ids, accountRepository::getAccounts, 30000);
    }
}

Or using lambdas:

import java.util.List;

public class AccountService {

    private AccountRepository accountRepository;

    public List<Account> getAccounts(List<Long> ids) {
        return PartitionArray.partitionAndDoBusinessFunction(ids, idList -> {
            List<Account> accounts = accountRepository.getAccounts(idList);
            // do more business on accounts
            return accounts;
        }, 30000);
    }
}
Benoit
  • 5,118
  • 2
  • 24
  • 43
  • 1
    This is very nice! (Hate the AspectJ solution, too overengineered, a lot of dependencies, config, etc). I would only add the partition size as an argument to the new method instead of hardcoding it to `30000`. – fps Oct 15 '18 at 14:38
  • 2
    @FedericoPeraltaSchaffner I fully agree with you - Lambda's and method references are made for such situations. Also I added `partitionSize` as parameter as you suggested. – Benoit Oct 15 '18 at 14:47
  • 1
    But this `AtomicInteger` based `partitionBasedOnSize` implementation is *horrible*. There is no need to iterate over all list elements (and updating an `AtomicInteger` for each), just to find out the number of elements. The list already knows. We can query it via `size()` and directly request a smaller part of it via `subList` for free (no copying happening). Just use `return IntStream.range(0, (inputList.size()+size-1)/size) .mapToObj(i -> inputList.subList(i*=size, Math.min(inputList.size(), i+size))) .collect(Collectors.toList());` to get a `List>` (even maintaining the order) – Holger Oct 15 '18 at 15:38
  • @Holger Thanks for your comment. I agree with you. I admit that I copy/pasted OP's implementation without challenging... Now updated with my own implementation. – Benoit Oct 16 '18 at 07:56
1

Here is a solution using annotations, using AspectJ :

First, define the desired annotation:

package partition;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Partitioned {

    /**
     * The size of the partition, for instance 30000
     */
    int size();
}

Thus your service will look like:

package partition;

import java.util.List;

public class AccountService {

    private AccountRepository accountRepository;

    public AccountService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @Partitioned(size = 30000)
    public List<Account> getAccounts(List<Long> ids) {
        return accountRepository.getAccounts(ids);
    }
}

So far it's easy. Then comes the processing of the annotation, where AspectJ enters the game. Define an aspect linked to the annotation:

package partition;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Aspect
public class PartitionedAspect {

    @Pointcut("@annotation(partitioned)")
    public void callAt(Partitioned partitioned) {
    }

    @Around("callAt(partitioned)")
    public <T, D> Object around(ProceedingJoinPoint pjp, Partitioned partitioned) throws Throwable {
        List<T> inputIds = (List) pjp.getArgs()[0];
        if (inputIds.size() > partitioned.size()) {
            List<D> dataList = new ArrayList<>();
            Collection<List<T>> partitionedIds = PartitionArray.partitionBasedOnSize(inputIds, partitioned.size());
            for (List<T> idList : partitionedIds) {
                List<D> data = (List) pjp.proceed(new Object[]{idList});
                dataList.addAll(data);
            }
            return dataList;
        }
        return pjp.proceed();
    }
}

Of course you have to import AspectJ, and also do some additional stuff at compile-time. Assuming you are using maven, add those lines to your pom.xml (plugin and dependencies):

<build>
    <plugins>
        ...
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.7</version>
            <configuration>
                <complianceLevel>1.8</complianceLevel>
                <source>1.8</source>
                <target>1.8</target>
                <showWeaveInfo>true</showWeaveInfo>
                <verbose>true</verbose>
                <Xlint>ignore</Xlint>
                <encoding>UTF-8</encoding>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

<dependencies>
    ...
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${aspectj.version}</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${aspectj.version}</version>
    </dependency>
...
</dependencies>
Benoit
  • 5,118
  • 2
  • 24
  • 43