Search code examples
javaimmutables-library

How would it be advised to get Immutables to build this object with this sort


How could I get Immutables to generate a class with this sort

public class IdentifiedUserDetails implements UserDetails, CredentialsContainer, Identified<UUID> {
    private static final long serialVersionUID = 4905378177558522349L;

    private final UUID id;
    private final String username;
    private final Set<GrantedAuthority> authorities;
    private boolean accountNonExpired = true;
    private boolean accountNonLocked = true;
    private boolean credentialsNonExpired = true;
    private boolean enabled = true;
    private String password;

    IdentifiedUserDetails( final UUID id, final String username, final String password ) {
        this.id = Objects.requireNonNull( id );
        this.username = Objects.requireNonNull( username );
        this.password = Objects.requireNonNull( password );
        this.authorities = Collections.unmodifiableSet( sortAuthorities( Collections.emptySet() ) );
    }

    private static SortedSet<GrantedAuthority> sortAuthorities(
        final Collection<? extends GrantedAuthority> authorities ) {
        // Ensure array iteration order is predictable (as per
        // UserDetails.getAuthorities() contract and SEC-717)
        return authorities.stream()
            .filter( Objects::nonNull )
            .collect( Collectors.toCollection( () -> {
                return new TreeSet<GrantedAuthority>(
                    Comparator.nullsFirst( Comparator.comparing( GrantedAuthority::getAuthority ) ) );
            } ) );
    }
}

note: the empty set is a placeholder for a real set passed in from some external data source, I haven't started populating it yet but I will at some point, and the sort is a re-implementation of the one in spring security, you should presume that the sort needs to be applied to any set that would be passed to the Immutables builder.


Solution

  • There are many variations on how you can achieve that using Immutables annotation processor, given that I understand what you trying to achieve ;)

    Immutables supports SortedSet out of the box, but only using natural ordering (see @Value.NaturalOrder and @Value.ReverseOrder). If you want to apply special comparator, Immutables will only allow you to build the set yourself and set it onto a builder. Judging from the example, it is desirable that ordering would be something specific to the implementation of the object, so I'll skip to other options.

    There's powerful (but somewhat error-prone) functionality to enable normalization/canonicalization of the object using @Value.Check method. It is described in the guide: http://immutables.github.io/immutable.html#normalization. However, using the normalization is a bit complicated by the need to check if a set/collection is already sorted.

    In the end, I would propose yet another, simpler approach, which I've used for similar purposes. @Value.Derived annotation allows you to build an alternative view of a data during object construction. In this case there will be collection used as initialization buffer and the computed, alternative view of this data. Computation will happen during construction and an immutable object will never change after that. We'll play with access and attribute names to make it look nice. Here's the example:

    @Value.Immutable
    public abstract class IdentifiedUserDetails implements UserDetails, CredentialsContainer, Identified<UUID> {
        private static final long serialVersionUID = 4905378177558522349L;
    
        public abstract UUID getId();
        public abstract String getUsername();
        public abstract String getPassword();
        // other attributes omitted for brevity
        // ...
    
        abstract @SkipNulls List<GrantedAuthority> authority();
    
        @Value.Derived
        public SortedSet<GrantedAuthority> getAuthorities() {
            return authority().stream()
                    .collect(Collectors.toCollection(() -> {
                        return new TreeSet(
                                Comparator.nullsFirst(Comparator.comparing(GrantedAuthority::getAuthority)));
                    }));
        }
    }
    
    public static void demonstration(GrantedAuthority ga1, GrantedAuthority ga2) {
    
        IdentifiedUserDetails details =
                ImmutableIdentifiedUserDetails.builder()
                        .id(UUID.randomUUID())
                        .username("Name")
                        .password("String")
                        //...
                        .addAuthority(ga1)
                        .addAuthority(ga2)
                        .build();
    
        SortedSet<GrantedAuthority> sortedAuthorities = details.getAuthorities();
    }
    

    P.S. @SkipNulls is a kind of BYOAnnotation. Create it, if needed, and it will be recognized by a simple name.