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}