001/*
002 * Copyright © 2025 CUI-OpenSource-Software (info@cuioss.de)
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 */
016package de.cuioss.test.generator.internal.net.java.quickcheck.characteristic;
017
018import de.cuioss.test.generator.internal.net.java.quickcheck.Characteristic;
019import de.cuioss.test.generator.internal.net.java.quickcheck.collection.Pair;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Map.Entry;
026
027/**
028 * Gather frequency information about test values.
029 * <p>
030 * {@link Classification}s are used to categories test cases. While
031 * {@link Characteristic#specify(Object)} is executed characteristic instances
032 * can add classifications with
033 * {@link Classification#doClassify(boolean, Object)}. For each execution of
034 * {@link Characteristic#specify(Object)} {@link Classification#call()} has to
035 * be executed once.
036 * </p>
037 */
038public class Classification {
039
040    private final Map<Object, Integer> classifications = new HashMap<>();
041    private int total;
042    private boolean reportState;
043
044    // derived value
045    private HashMap<Object, Double> classifiedWithPercents;
046    // derived value
047    private ArrayList<Object> sortedCategories;
048
049    /**
050     * Get the frequency of the given category.
051     *
052     * @return per cent of test cases with this classification.
053     */
054    public double getFrequency(Object classification) {
055        reportState();
056        Double frequency = getClassified().get(classification);
057        return frequency == null ? 0 : frequency;
058    }
059
060    /**
061     * Get a list of known categories. Categories are sorted in descending
062     * frequency.
063     */
064    public List<Object> getCategories() {
065        reportState();
066        if (sortedCategories == null) {
067            ArrayList<Pair<Object, Double>> toSort = buildCategoryFrequencyList();
068            sortByFrequency(toSort);
069            sortedCategories = transFormPairListToCategoryList(toSort);
070        }
071        return sortedCategories;
072    }
073
074    @Override
075    public String toString() {
076        StringBuilder builder = new StringBuilder();
077        Map<Object, Double> classified = getClassified();
078        builder.append("Classifications :");
079        List<Object> categories = getCategories();
080        for (Object key : categories) {
081            builder.append("%n%s = %1.2f%%".formatted(key, classified.get(key)));
082        }
083        if (classified.isEmpty()) {
084            builder.append("none");
085        }
086        return builder.toString();
087    }
088
089    public void doClassify(Object classification) {
090        doClassify(true, classification);
091    }
092
093    /**
094     * Increment the classification counter for the given classification.
095     *
096     * @param predicate      increment only if the predicate is true.
097     * @param classification classification key
098     */
099    public void doClassify(boolean predicate, Object classification) {
100        checkReportState();
101        if (!predicate)
102            return;
103        boolean categoryFound = classifications.containsKey(classification);
104        int current = categoryFound ? classifications.get(classification) : 0;
105        classifications.put(classification, ++current);
106    }
107
108    /**
109     * Count the number of calls.
110     */
111    public void call() {
112        checkReportState();
113        total++;
114    }
115
116    private Map<Object, Double> getClassified() {
117        if (classifiedWithPercents == null) {
118            classifiedWithPercents = new HashMap<>();
119            for (Entry<Object, Integer> entry : classifications.entrySet()) {
120                Object key = entry.getKey();
121                Integer count = entry.getValue();
122                double percent = (double) count * 100 / total;
123                classifiedWithPercents.put(key, percent);
124            }
125        }
126        return classifiedWithPercents;
127    }
128
129    private void reportState() {
130        reportState = true;
131    }
132
133    private ArrayList<Pair<Object, Double>> buildCategoryFrequencyList() {
134        Map<Object, Double> classified = getClassified();
135        ArrayList<Pair<Object, Double>> toSort = new ArrayList<>();
136        for (Entry<Object, Double> e : classified.entrySet()) {
137            toSort.add(new Pair<>(e.getKey(), e.getValue()));
138        }
139        return toSort;
140    }
141
142    private ArrayList<Object> transFormPairListToCategoryList(ArrayList<Pair<Object, Double>> toSort) {
143        ArrayList<Object> sortedCategoryList = new ArrayList<>();
144        for (Pair<Object, Double> pair : toSort) {
145            sortedCategoryList.add(pair.getFirst());
146        }
147        return sortedCategoryList;
148    }
149
150    private void sortByFrequency(ArrayList<Pair<Object, Double>> toSort) {
151        toSort.sort((o1, o2) -> o2.getSecond().compareTo(o1.getSecond()));
152    }
153
154    // this is the easiest implementation report calculation can be resource
155    // demanding
156    private void checkReportState() {
157        if (reportState)
158            throw new IllegalStateException("do not call after report was started.");
159    }
160
161    public void classifyCall(boolean predicate, Object classification) {
162        doClassify(predicate, classification);
163        call();
164    }
165
166    public void classifyCall(Object classification) {
167        classifyCall(true, classification);
168    }
169}