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}