001// --------------------------------------------------------------------------------
002// Copyright 2002-2025 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.EncryptionUtils;
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;
031
032public class PasswordGeneratorUtils {
033    
034    private PasswordGeneratorUtils() {
035        super();
036    }
037    
038    private static class PasswordGeneratorUtilsHolder {
039        static PasswordGeneratorUtils instance = new PasswordGeneratorUtils();
040    }
041    
042    public static PasswordGeneratorUtils getInstance() {
043        return PasswordGeneratorUtilsHolder.instance;
044    }
045
046    private final static List<Graph> graphs;
047    
048    static {
049        graphs = Collections.unmodifiableList(Arrays.asList(
050                new Graph("a", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("a"))),
051                new Graph("A", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("a", "ae", "ai"))),
052                new Graph("b", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("b"))),
053                new Graph("ch", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("ch"))),
054                new Graph("d", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("d"))),
055                new Graph("e", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("e"))),
056                new Graph("E", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("e", "ee", "ie"))),
057                new Graph("f", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("f", "ph", "gh"))),
058                new Graph("g", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("g"))),
059                new Graph("h", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("h"))),
060                new Graph("i", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("i", "e"))),
061                new Graph("I", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("i", "ai"))),
062                new Graph("i", GraphType.VOWEL_OTHER, Collections.unmodifiableList(Arrays.asList("i", "ei"))),
063                new Graph("j", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("j", "g"))),
064                new Graph("k", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("k", "c"))),
065                new Graph("l", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("l"))),
066                new Graph("m", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("m"))),
067                new Graph("n", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("n"))),
068                new Graph("ng", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("ng"))),
069                new Graph("o", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("o", "a", "ah"))),
070                new Graph("O", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("o", "oh"))),
071                new Graph("oo", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("oo", "u"))),
072                new Graph("OO", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("oo", "w"))),
073                new Graph("p", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("p"))),
074                new Graph("qu", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("qu"))),
075                new Graph("r", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("r"))),
076                new Graph("s", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("s", "c"))),
077                new Graph("sh", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("sh", "s"))),
078                new Graph("t", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("t"))),
079                new Graph("th", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("th"))),
080                new Graph("TH", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("th"))),
081                new Graph("u", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("u"))),
082                new Graph("U", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("u", "oo"))),
083                new Graph("v", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("v"))),
084                new Graph("x", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("x"))),
085                new Graph("y", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("y"))),
086                new Graph("z", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("z", "s")))
087                ));
088    }
089    
090    private Graph selectGraph(Random random, List<Graph> graphs) {
091        return graphs.get(random.nextInt(graphs.size()));
092    }
093    
094    private String selectSpelling(Random random, Graph graph) {
095        return graph.spellings.get(random.nextInt(graph.spellings.size()));
096    }
097    
098    private Set<GraphType> chooseNext(Random random, GraphType previous, GraphType current) {
099        Set<GraphType> graphTypes;
100        
101        if(current == GraphType.CONSONANT) {
102            graphTypes = EnumSet.of(GraphType.VOWEL_SHORT, GraphType.VOWEL_LONG, GraphType.VOWEL_OTHER);
103        } else if(previous == null || previous == GraphType.VOWEL_SHORT || previous == GraphType.VOWEL_LONG || previous == GraphType.VOWEL_OTHER) {
104            graphTypes = EnumSet.of(GraphType.CONSONANT);
105        } else if(random.nextBoolean()) {
106            graphTypes = EnumSet.of(GraphType.VOWEL_SHORT, GraphType.VOWEL_LONG, GraphType.VOWEL_OTHER);
107            graphTypes.remove(current);
108        } else {
109            graphTypes = EnumSet.of(GraphType.CONSONANT);
110        }
111        
112        return graphTypes;
113    }
114    
115    public String getPassword(int minimumLength) {
116        var random = EncryptionUtils.getInstance().getRandom();
117        Graph previous = null;
118        var current = selectGraph(random, graphs);
119        var password = new StringBuilder();
120        
121        while(password.length() < minimumLength) {
122            var nextGraphTypes = chooseNext(random, previous == null ? null : previous.graphType, current.graphType);
123            List<Graph> nextGraphs = new ArrayList<>(graphs.size());
124            
125            password.append(selectSpelling(random, current));
126            
127            graphs.stream().filter((graph) -> nextGraphTypes.contains(graph.graphType)).forEach((graph) -> {
128                nextGraphs.add(graph);
129            });
130            
131            previous = current;
132            current = selectGraph(random, nextGraphs);
133        }
134        
135        return password.toString();
136    }
137    
138    public String getPassword(PartyType partyType) {
139        var partyControl = Session.getModelController(PartyControl.class);
140        var partyTypePasswordStringPolicyDetail = partyControl.getPartyTypePasswordStringPolicy(partyType).getLastDetail();
141        var intMinimumLength = partyTypePasswordStringPolicyDetail.getMinimumLength();
142        var intMaximumLength = partyTypePasswordStringPolicyDetail.getMaximumLength();
143        var minimumLength = intMinimumLength == null ? 0 : intMinimumLength;
144        var maximumLength = intMaximumLength == null ? 38 : intMaximumLength;
145        
146        return getPassword(minimumLength + (maximumLength - minimumLength) / 4);
147    }
148    
149}