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}