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