001/*
002 * Licensed to the author under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package de.cuioss.test.generator.internal.net.java.quickcheck.characteristic;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024
025import de.cuioss.test.generator.internal.net.java.quickcheck.Characteristic;
026import de.cuioss.test.generator.internal.net.java.quickcheck.collection.Pair;
027
028/**
029 * Gather frequency information about test values.
030 * <p>
031 * {@link Classification}s are used to categories test cases. While
032 * {@link Characteristic#specify(Object)} is executed characteristic instances
033 * can add classifications with
034 * {@link Classification#doClassify(boolean, Object)}. For each execution of
035 * {@link Characteristic#specify(Object)} {@link Classification#call()} has to
036 * be executed once.
037 * </p>
038 */
039public class Classification {
040
041    private final Map<Object, Integer> classifications = new HashMap<>();
042    private int total;
043    private boolean reportState;
044
045    // derived value
046    private HashMap<Object, Double> classifiedWithPercents;
047    // derived value
048    private ArrayList<Object> sortedCategories;
049
050    /**
051     * Get the frequency of the given category.
052     *
053     * @return per cent of test cases with this classification.
054     */
055    public double getFrequency(Object classification) {
056        reportState();
057        Double frequency = getClassified().get(classification);
058        return frequency == null ? 0 : frequency;
059    }
060
061    /**
062     * Get a list of known categories. Categories are sorted in descending
063     * frequency.
064     */
065    public List<Object> getCategories() {
066        reportState();
067        if (sortedCategories == null) {
068            ArrayList<Pair<Object, Double>> toSort = buildCategoryFrequencyList();
069            sortByFrequency(toSort);
070            sortedCategories = transFormPairListToCategoryList(toSort);
071        }
072        return sortedCategories;
073    }
074
075    @Override
076    public String toString() {
077        StringBuilder builder = new StringBuilder();
078        Map<Object, Double> classified = getClassified();
079        builder.append("Classifications :");
080        List<Object> categories = getCategories();
081        for (Object key : categories) {
082            builder.append("%n%s = %1.2f%%".formatted(key, classified.get(key)));
083        }
084        if (classified.isEmpty()) {
085            builder.append("none");
086        }
087        return builder.toString();
088    }
089
090    public void doClassify(Object classification) {
091        doClassify(true, classification);
092    }
093
094    /**
095     * Increment the classification counter for the given classification.
096     *
097     * @param predicate      increment only if the predicate is true.
098     * @param classification classification key
099     */
100    public void doClassify(boolean predicate, Object classification) {
101        checkReportState();
102        if (!predicate)
103            return;
104        boolean categoryFound = classifications.containsKey(classification);
105        int current = categoryFound ? classifications.get(classification) : 0;
106        classifications.put(classification, ++current);
107    }
108
109    /**
110     * Count the number of calls.
111     */
112    public void call() {
113        checkReportState();
114        total++;
115    }
116
117    private Map<Object, Double> getClassified() {
118        if (classifiedWithPercents == null) {
119            classifiedWithPercents = new HashMap<>();
120            for (Entry<Object, Integer> entry : classifications.entrySet()) {
121                Object key = entry.getKey();
122                Integer count = entry.getValue();
123                double percent = (double) count * 100 / total;
124                classifiedWithPercents.put(key, percent);
125            }
126        }
127        return classifiedWithPercents;
128    }
129
130    private void reportState() {
131        reportState = true;
132    }
133
134    private ArrayList<Pair<Object, Double>> buildCategoryFrequencyList() {
135        Map<Object, Double> classified = getClassified();
136        ArrayList<Pair<Object, Double>> toSort = new ArrayList<>();
137        for (Entry<Object, Double> e : classified.entrySet()) {
138            toSort.add(new Pair<>(e.getKey(), e.getValue()));
139        }
140        return toSort;
141    }
142
143    private ArrayList<Object> transFormPairListToCategoryList(ArrayList<Pair<Object, Double>> toSort) {
144        ArrayList<Object> sortedCategoryList = new ArrayList<>();
145        for (Pair<Object, Double> pair : toSort) {
146            sortedCategoryList.add(pair.getFirst());
147        }
148        return sortedCategoryList;
149    }
150
151    private void sortByFrequency(ArrayList<Pair<Object, Double>> toSort) {
152        toSort.sort((o1, o2) -> o2.getSecond().compareTo(o1.getSecond()));
153    }
154
155    // this is the easiest implementation report calculation can be resource
156    // demanding
157    private void checkReportState() {
158        if (reportState)
159            throw new IllegalStateException("do not call after report was started.");
160    }
161
162    public void classifyCall(boolean predicate, Object classification) {
163        doClassify(predicate, classification);
164        call();
165    }
166
167    public void classifyCall(Object classification) {
168        classifyCall(true, classification);
169    }
170}