001// --------------------------------------------------------------------------------
002// Copyright 2002-2026 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.util.server.string;
018
019import com.echothree.model.control.party.server.control.PartyControl;
020import com.echothree.model.data.party.server.entity.PartyType;
021import com.echothree.util.server.persistence.RandomUtils;
022import com.echothree.util.server.persistence.Session;
023import com.echothree.util.server.string.Graph.GraphType;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.EnumSet;
028import java.util.List;
029import java.util.Random;
030import java.util.Set;
031import javax.enterprise.context.ApplicationScoped;
032import javax.inject.Inject;
033
034@ApplicationScoped
035public class PasswordGeneratorUtils {
036
037    @Inject
038    RandomUtils randomUtils;
039
040    protected PasswordGeneratorUtils() {
041        super();
042    }
043
044    private final static List<Graph> graphs;
045    
046    static {
047        graphs = Collections.unmodifiableList(Arrays.asList(
048                new Graph("a", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("a"))),
049                new Graph("A", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("a", "ae", "ai"))),
050                new Graph("b", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("b"))),
051                new Graph("ch", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("ch"))),
052                new Graph("d", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("d"))),
053                new Graph("e", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("e"))),
054                new Graph("E", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("e", "ee", "ie"))),
055                new Graph("f", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("f", "ph", "gh"))),
056                new Graph("g", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("g"))),
057                new Graph("h", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("h"))),
058                new Graph("i", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("i", "e"))),
059                new Graph("I", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("i", "ai"))),
060                new Graph("i", GraphType.VOWEL_OTHER, Collections.unmodifiableList(Arrays.asList("i", "ei"))),
061                new Graph("j", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("j", "g"))),
062                new Graph("k", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("k", "c"))),
063                new Graph("l", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("l"))),
064                new Graph("m", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("m"))),
065                new Graph("n", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("n"))),
066                new Graph("ng", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("ng"))),
067                new Graph("o", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("o", "a", "ah"))),
068                new Graph("O", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("o", "oh"))),
069                new Graph("oo", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("oo", "u"))),
070                new Graph("OO", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("oo", "w"))),
071                new Graph("p", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("p"))),
072                new Graph("qu", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("qu"))),
073                new Graph("r", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("r"))),
074                new Graph("s", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("s", "c"))),
075                new Graph("sh", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("sh", "s"))),
076                new Graph("t", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("t"))),
077                new Graph("th", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("th"))),
078                new Graph("TH", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("th"))),
079                new Graph("u", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("u"))),
080                new Graph("U", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("u", "oo"))),
081                new Graph("v", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("v"))),
082                new Graph("x", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("x"))),
083                new Graph("y", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("y"))),
084                new Graph("z", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("z", "s")))
085                ));
086    }
087    
088    private Graph selectGraph(Random random, List<Graph> graphs) {
089        return graphs.get(random.nextInt(graphs.size()));
090    }
091    
092    private String selectSpelling(Random random, Graph graph) {
093        return graph.spellings.get(random.nextInt(graph.spellings.size()));
094    }
095    
096    private Set<GraphType> chooseNext(Random random, GraphType previous, GraphType current) {
097        Set<GraphType> graphTypes;
098        
099        if(current == GraphType.CONSONANT) {
100            graphTypes = EnumSet.of(GraphType.VOWEL_SHORT, GraphType.VOWEL_LONG, GraphType.VOWEL_OTHER);
101        } else if(previous == null || previous == GraphType.VOWEL_SHORT || previous == GraphType.VOWEL_LONG || previous == GraphType.VOWEL_OTHER) {
102            graphTypes = EnumSet.of(GraphType.CONSONANT);
103        } else if(random.nextBoolean()) {
104            graphTypes = EnumSet.of(GraphType.VOWEL_SHORT, GraphType.VOWEL_LONG, GraphType.VOWEL_OTHER);
105            graphTypes.remove(current);
106        } else {
107            graphTypes = EnumSet.of(GraphType.CONSONANT);
108        }
109        
110        return graphTypes;
111    }
112    
113    public String getPassword(int minimumLength) {
114        var random = randomUtils.getRandom();
115        Graph previous = null;
116        var current = selectGraph(random, graphs);
117        var password = new StringBuilder();
118        
119        while(password.length() < minimumLength) {
120            var nextGraphTypes = chooseNext(random, previous == null ? null : previous.graphType, current.graphType);
121            List<Graph> nextGraphs = new ArrayList<>(graphs.size());
122            
123            password.append(selectSpelling(random, current));
124            
125            graphs.stream().filter((graph) -> nextGraphTypes.contains(graph.graphType)).forEach((graph) -> {
126                nextGraphs.add(graph);
127            });
128            
129            previous = current;
130            current = selectGraph(random, nextGraphs);
131        }
132        
133        return password.toString();
134    }
135    
136    public String getPassword(PartyType partyType) {
137        var partyControl = Session.getModelController(PartyControl.class);
138        var partyTypePasswordStringPolicyDetail = partyControl.getPartyTypePasswordStringPolicy(partyType).getLastDetail();
139        var intMinimumLength = partyTypePasswordStringPolicyDetail.getMinimumLength();
140        var intMaximumLength = partyTypePasswordStringPolicyDetail.getMaximumLength();
141        var minimumLength = intMinimumLength == null ? 0 : intMinimumLength;
142        var maximumLength = intMaximumLength == null ? 38 : intMaximumLength;
143        
144        return getPassword(minimumLength + (maximumLength - minimumLength) / 4);
145    }
146    
147}