tfcache

code style. removed forwarding methods. inline conditions.

1/10/2021 4:00:38 AM

Details

diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Arguments.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Arguments.java
index 8d7d6bd..f39042b 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Arguments.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Arguments.java
@@ -7,18 +7,13 @@ import java.util.stream.Stream;
 public class Arguments {
 
     public static Map<String, String> parse(String[] args) {
-        Map<String, String> arguments = Stream.of(args).map(arg -> {
+        return Stream.of(args).map(arg -> {
             arg = arg.replaceFirst("--", "");
             int indexOf = arg.indexOf("=");
             if (indexOf == -1) {
                 return new String[]{arg, ""};
             }
             return new String[]{arg.substring(0, indexOf), arg.substring(indexOf + 1)};
-        }).collect(Collectors.toMap(array -> {
-            return array[0];
-        }, array -> {
-            return array[1];
-        }));
-        return arguments;
+        }).collect(Collectors.toMap(array -> array[0], array -> array[1]));
     }
 }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Configuration.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Configuration.java
index 0c64f2d..f4ae3b7 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Configuration.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Configuration.java
@@ -19,7 +19,7 @@ public class Configuration {
     private static String changeability = "allow";
     private static String staleness = "ignore";
     private static String kernel = "exhaustive";
-    private static ArrayList<Double> preferences = new ArrayList<>(List.of(1D, 0D));
+    private static final ArrayList<Double> preferences = new ArrayList<>(List.of(1D, 0D));
     private static boolean verbose = false;
 
     public static void process(String[] args) {
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java
index b385b41..d50ca71 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java
@@ -25,15 +25,15 @@ public class Main {
         System.setProperty("java.util.logging.SimpleFormatter.format", "[%1$tF %1$tT+%1$tL] [%4$-7s] [TFCache] %5$s %n");
 
         if (args.length < 1) {
-            System.err.println("--trace=<TracePath> " +
-                    "[--store=<storePath>] " +
-                    "[--level=<method|input>] " +
-                    "[--output=<outputPath>] " +
-                    "[--changeability=<allow|deny>] " +
-                    "[--staleness=<ignore|shrink>] " +
-                    "[--kernel=<exhaustive|optimised>|test] " +
-                    "[--preferences=(savedTime)0..1,0..1(idleTime)] " +
-                    "[--verbose=<false|true>]");
+            System.err.println("--trace=<TracePath>" +
+                    " [--store=<storePath>]" +
+                    " [--level=<method|input>]" +
+                    " [--output=<outputPath>]" +
+                    " [--changeability=<allow|deny>]" +
+                    " [--staleness=<ignore|shrink>]" +
+                    " [--kernel=<exhaustive|optimised>|test]" +
+                    " [--preferences=(savedTime)0..1,0..1(idleTime)]" +
+                    " [--verbose=<false|true>]");
             System.exit(1);
         }
 
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/GroupOfOccurrences.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/GroupOfOccurrences.java
index 65d1d7d..9a0202c 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/GroupOfOccurrences.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/GroupOfOccurrences.java
@@ -1,8 +1,3 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
 package br.ufrgs.inf.prosoft.tfcache.metadata;
 
 import br.ufrgs.inf.prosoft.tfcache.Metrics;
@@ -10,7 +5,6 @@ import br.ufrgs.inf.prosoft.tfcache.Pareto;
 import br.ufrgs.inf.prosoft.tfcache.Simulator;
 import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
 
-import java.math.BigDecimal;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
@@ -19,89 +13,59 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.Stream;
 
-/**
- *
- * @author romulo
- */
 public class GroupOfOccurrences {
 
-    private static final Logger LOGGER = Logger.getLogger(GroupOfOccurrences.class.getName());
+  private static final Logger LOGGER = Logger.getLogger(GroupOfOccurrences.class.getName());
+  private final List<Occurrence> occurrences;
+  private final String parameters;
+  private Pareto pareto;
 
-    private final List<Occurrence> occurrences;
-    private final String parameters;
+  public GroupOfOccurrences(String parameters, List<Occurrence> occurrences) {
+    this.parameters = parameters;
+    this.occurrences = occurrences;
+  }
 
-    private Pareto pareto;
+  public String getParameters() {
+    return this.parameters;
+  }
 
-    public GroupOfOccurrences(String parameters, List<Occurrence> occurrences) {
-        this.parameters = parameters;
-        this.occurrences = occurrences;
+  public Metrics getBestMetrics() {
+    if (this.pareto == null) {
+      throw new RuntimeException("simulation must be executed");
     }
-
-    public String getParameters() {
-        return this.parameters;
+    return this.pareto.getBestMetrics();
+  }
+
+  public Stream<Occurrence> occurrences() {
+    return this.occurrences.stream();
+  }
+
+  public boolean isChangeable() {
+    Iterator<Occurrence> iterator = this.occurrences.iterator();
+    Occurrence first = iterator.next();
+    while (iterator.hasNext()) {
+      Occurrence next = iterator.next();
+      if (!Objects.deepEquals(first.getReturnValue(), next.getReturnValue())) {
+        return true;
+      }
     }
+    return false;
+  }
 
-    public int getOccurrencesSize() {
-        return this.occurrences.size();
+  public void calculateMetrics() {
+    if (this.pareto != null) {
+      LOGGER.log(Level.WARNING, "metrics already calculated");
     }
-
-    public Metrics getBestMetrics() {
-        if (this.pareto == null) {
-            throw new RuntimeException("simulation must be executed");
-        }
-        return this.pareto.getBestMetrics();
+    if (this.occurrences.size() < 2) {
+      throw new RuntimeException("Not reusable input");
     }
-
-    public long getTtl() {
-        return getBestMetrics().getTtl();
-    }
-
-    public long getHits() {
-        return getBestMetrics().getHits();
-    }
-
-    public long getStales() {
-        return getBestMetrics().getStales();
-    }
-
-    public double getSavedTime() {
-        return getBestMetrics().getSavedTime();
-    }
-
-    public BigDecimal getSavedTimePerTimeInCache() {
-        return getBestMetrics().getSavedTimePerTimeInCache();
-    }
-
-    public Stream<Occurrence> occurrences() {
-        return this.occurrences.stream();
-    }
-
-    public boolean isChangeable() {
-        Iterator<Occurrence> iterator = this.occurrences.iterator();
-        Occurrence first = iterator.next();
-        while (iterator.hasNext()) {
-            Occurrence next = iterator.next();
-            if (!Objects.deepEquals(first.getReturnValue(), next.getReturnValue())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void calculateMetrics() {
-        if (this.pareto != null) {
-            LOGGER.log(Level.WARNING, "metrics already calculated");
-        }
-        if (this.occurrences.size() < 2) {
-            throw new RuntimeException("Not reusable input");
-        }
-        this.occurrences.sort(Comparator.comparingLong(Occurrence::getStartTime));
-        this.pareto = new Pareto();
-        Simulator.simulate(this.occurrences, this.pareto);
-        if (Configuration.getVerbose()) {
-            this.pareto.values().forEach(it ->
-                    System.out.println(Configuration.getInput() + "," + it.getTtl() + "," + it.getSavedTime() + "," + it.getHits() + "," + it.getTimeInCache()));
-        }
+    this.occurrences.sort(Comparator.comparingLong(Occurrence::getStartTime));
+    this.pareto = new Pareto();
+    Simulator.simulate(this.occurrences, this.pareto);
+    if (Configuration.getVerbose()) {
+      this.pareto.values().forEach(it ->
+        System.out.println(Configuration.getInput() + "," + it.getTtl() + "," + it.getSavedTime() + "," + it.getHits() + "," + it.getTimeInCache()));
     }
+  }
 
 }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Method.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Method.java
index 84fd1aa..4071323 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Method.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Method.java
@@ -1,8 +1,3 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
 package br.ufrgs.inf.prosoft.tfcache.metadata;
 
 import br.ufrgs.inf.prosoft.tfcache.Metrics;
@@ -11,237 +6,159 @@ import br.ufrgs.inf.prosoft.tfcache.Simulator;
 import br.ufrgs.inf.prosoft.tfcache.StorageManager;
 import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
 
-import java.math.BigDecimal;
+import java.text.MessageFormat;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-/**
- * @author romulo
- */
 public class Method {
 
-    private static final Logger LOGGER = Logger.getLogger(Method.class.getName());
-
-    private final String name;
-    private final List<Occurrence> occurrences;
-    private List<GroupOfOccurrences> groupsOfOccurrences;
-
-    private Integer countChangeableGroups;
-
-    private Pareto pareto;
-
-    public Method(String name, List<Occurrence> occurrences) {
-        this.name = name;
-        if (occurrences == null) {
-            throw new RuntimeException("Occurrences is null");
-        }
-        if (occurrences.isEmpty()) {
-            throw new RuntimeException("Occurrences is empty");
-        }
-        this.occurrences = occurrences;
-    }
-
-    public static Map<String, List<Occurrence>> groupByInput(List<Occurrence> occurrences) {
-        if (occurrences == null) {
-            throw new RuntimeException("Occurrences cannot be null");
-        }
-        Map<String, List<Occurrence>> inputHasOccurrences = new ConcurrentHashMap<>();
-        occurrences.forEach(occurrence -> {
-            String parameters = occurrence.getParametersSerialised();
-
-            inputHasOccurrences.compute(parameters, (key, oldValue) -> {
-                if (oldValue == null) {
-                    oldValue = new ArrayList<>();
-                }
-                oldValue.add(occurrence);
-                return oldValue;
-            });
-        });
-        return inputHasOccurrences;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public Metrics getBestMetrics() {
-        return this.pareto.getBestMetrics();
-    }
-
-    public double getEstimatedSavedTime() {
-        if (getBestMetrics() != null) {
-            return getBestMetrics().getSavedTime();
-        }
-        return groupsOfOccurrences().map(GroupOfOccurrences::getSavedTime).reduce(Double::sum).orElse(0D);
-    }
-
-    public BigDecimal getEstimatedSavedTimePerTimeInCache() {
-        if (getBestMetrics() != null) {
-            return getBestMetrics().getSavedTimePerTimeInCache();
-        }
-        GroupOfOccurrences max = groupsOfOccurrences().max(Comparator.comparing(GroupOfOccurrences::getSavedTimePerTimeInCache)).orElseThrow();
-        return max.getSavedTimePerTimeInCache();
-    }
-
-    public Stream<Occurrence> occurrences() {
-        if (this.occurrences == null) {
-            throw new RuntimeException("Occurrences already consumed");
-        }
-        return this.occurrences.stream();
-    }
-
-    public int getOccurrencesSize() {
-        if (this.occurrences == null) {
-            if (this.groupsOfOccurrences == null) {
-                throw new RuntimeException("groupsOfOccurrences is null");
-            }
-            return this.groupsOfOccurrences.stream().map(GroupOfOccurrences::getOccurrencesSize).reduce(Integer::sum).orElse(0);
-        }
-        return this.occurrences.size();
-    }
-
-    public Stream<GroupOfOccurrences> groupsOfOccurrences() {
-        if (this.groupsOfOccurrences == null) {
-            groupByInput();
-        }
-        return this.groupsOfOccurrences.stream();
-    }
-
-    private void groupByInput() {
-        if (this.occurrences == null) {
-            throw new RuntimeException("Occurrences already consumed");
-        }
-        this.groupsOfOccurrences = groupByInput(this.occurrences).entrySet().stream()
-                .map(entry -> new GroupOfOccurrences(entry.getKey(), entry.getValue()))
-                .collect(Collectors.toList());
-    }
-
-    public void removeChangeableInputs() {
-        if (this.countChangeableGroups != null) {
-            throw new RuntimeException("Changeable already filtered");
-        }
-        if (this.groupsOfOccurrences == null) {
-            groupByInput();
-        }
-        int initialCountOfInputs = this.groupsOfOccurrences.size();
-        this.groupsOfOccurrences.removeIf(GroupOfOccurrences::isChangeable);
-        this.countChangeableGroups = initialCountOfInputs - this.groupsOfOccurrences.size();
-        if (this.countChangeableGroups > 0) {
-            LOGGER.log(Level.INFO, "\tRemoved {0} of {1} changeable inputs from method {2}", new Object[]{this.countChangeableGroups, initialCountOfInputs, this.name});
-        }
-    }
-
-    public boolean isChangeable() {
-        if (this.countChangeableGroups != null) {
-            return this.countChangeableGroups > 0;
-        }
-        if (this.groupsOfOccurrences != null) {
-            return this.groupsOfOccurrences.stream().anyMatch(GroupOfOccurrences::isChangeable);
-        }
-        Map<String, Object> inputHasOutput = new HashMap<>();
-        for (Occurrence occurrence : this.occurrences) {
-            String parameters = occurrence.getParametersSerialised();
-            if (!inputHasOutput.containsKey(parameters)) {
-                inputHasOutput.put(parameters, occurrence.getReturnValue());
-            } else if (!Objects.deepEquals(inputHasOutput.get(parameters), occurrence.getReturnValue())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void removeSingleOccurrences() {
-        if (this.groupsOfOccurrences == null) {
-            groupByInput();
-        }
-        int initialCountofOccurrences = this.groupsOfOccurrences.size();
-        this.groupsOfOccurrences.removeIf(groupOfOccurrences -> groupOfOccurrences.getOccurrencesSize() < 2);
-        int removedOccurrences = initialCountofOccurrences - this.groupsOfOccurrences.size();
-        if (removedOccurrences > 0) {
-            LOGGER.log(Level.INFO, "\tRemoved {0} of {1} inputs from method {2}", new Object[]{removedOccurrences, initialCountofOccurrences, this.name});
-        }
-    }
-
-    public boolean isReusable() {
-        if (this.groupsOfOccurrences != null) {
-            return this.groupsOfOccurrences.stream().anyMatch(groupOfOccurrences -> groupOfOccurrences.getOccurrencesSize() > 1);
-        }
-        Map<String, Long> inputHasFrequency = occurrences()
-                .map(Occurrence::getParametersSerialised)
-                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
-        return inputHasFrequency.values().stream().anyMatch(frequency -> frequency > 1);
-    }
-
-    public boolean isNotReusable() {
-        return !isReusable();
-    }
-
-    public void recommendTTL() {
-        if (this.pareto != null) {
-            LOGGER.log(Level.WARNING, "metrics already calculated");
-        }
-        if (this.occurrences == null) {
-            throw new RuntimeException("groupByInputs called. Cannot proceed");
-        }
-        this.pareto = new Pareto();
-        if (this.occurrences.size() < 2) {
-            return;
-        }
-        this.occurrences.sort(Comparator.comparingLong(Occurrence::getStartTime));
-        Simulator.simulate(this.occurrences, this.pareto);
-        if (Configuration.getVerbose()) {
-            String application = Configuration.getInput().split(",")[0];
-            this.pareto.values().forEach(it ->
-                    System.out.println(application + "," + name + "," + it.getTtl() + "," + it.getSavedTime() + "," + it.getHits() + "," + it.getTimeInCache()));
-        }
-    }
-
-    public void recommendTTLPerInput() {
-        if (this.groupsOfOccurrences == null) {
-            groupByInput();
-            removeSingleOccurrences();
-        }
-        if (Configuration.getVerbose()) {
-            System.out.println("=== " + getName() + " ===");
-            Configuration.setInput(Configuration.getInput().split(",")[0] + "," + getName() + ",");
-            groupsOfOccurrences().forEach(GroupOfOccurrences::calculateMetrics);
-        } else {
-            groupsOfOccurrences().parallel().forEach(GroupOfOccurrences::calculateMetrics);
-        }
-        String uuid = Configuration.getUUID().replace("level:input", "level:method");
-        Pareto pareto = StorageManager.get(uuid, this.occurrences);
-        if (this.pareto == null && pareto != null) {
-            this.pareto = pareto;
-        }
-    }
-
-    public void removeNotRecommendedInputs() {
-        if (this.groupsOfOccurrences == null) {
-            throw new RuntimeException("Recommendations not called");
-        }
-        int initialCountofOccurrences = this.groupsOfOccurrences.size();
-        this.groupsOfOccurrences.removeIf(groupOfOccurrences -> groupOfOccurrences.getSavedTime() == 0);
-        int removedOccurrences = initialCountofOccurrences - this.groupsOfOccurrences.size();
-        if (removedOccurrences > 0) {
-            LOGGER.log(Level.INFO, "\tRemoved {0} of {1} inputs from method {2}", new Object[]{removedOccurrences, initialCountofOccurrences, this.name});
-        }
-    }
-
-    public void rankRecommendations() {
-        if (this.groupsOfOccurrences == null) {
-            throw new RuntimeException("groupsOfOccurrences is null");
-        }
-        this.groupsOfOccurrences.sort((group1, group2) -> group2.getSavedTimePerTimeInCache().compareTo(group1.getSavedTimePerTimeInCache()));
-    }
-
-    @Override
-    public String toString() {
-        return this.name;
-    }
+  private static final Logger LOGGER = Logger.getLogger(Method.class.getName());
+  private final String name;
+  private final List<Occurrence> occurrences;
+  private List<GroupOfOccurrences> groupsOfOccurrences;
+  private Integer countChangeableGroups;
+  private Pareto pareto;
+
+  public Method(String name, List<Occurrence> occurrences) {
+    this.name = name;
+    if (occurrences == null) throw new RuntimeException("Occurrences is null");
+    if (occurrences.isEmpty()) throw new RuntimeException("Occurrences is empty");
+    this.occurrences = occurrences;
+  }
+
+  public static Map<String, List<Occurrence>> groupByInput(List<Occurrence> occurrences) {
+    Map<String, List<Occurrence>> inputHasOccurrences = new ConcurrentHashMap<>();
+    occurrences.forEach(occurrence -> {
+      String parameters = occurrence.getParametersSerialised();
+      inputHasOccurrences.compute(parameters, (key, oldValue) -> {
+        if (oldValue == null) oldValue = new ArrayList<>();
+        oldValue.add(occurrence);
+        return oldValue;
+      });
+    });
+    return inputHasOccurrences;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Metrics getBestMetrics() {
+    if (this.pareto == null) throw new RuntimeException("trying to access pareto before calculating it");
+    return this.pareto.getBestMetrics();
+  }
+
+  public double getEstimatedSavedTime() {
+    if (getBestMetrics() != null) return getBestMetrics().getSavedTime();
+    return groupsOfOccurrences().map(it -> it.getBestMetrics().getSavedTime()).reduce(Double::sum).orElse(0D);
+  }
+
+  public Stream<Occurrence> occurrences() {
+    return this.occurrences.stream();
+  }
+
+  public Stream<GroupOfOccurrences> groupsOfOccurrences() {
+    if (this.groupsOfOccurrences == null) groupByInput();
+    return this.groupsOfOccurrences.stream();
+  }
+
+  private void groupByInput() {
+    this.groupsOfOccurrences = groupByInput(this.occurrences).entrySet().stream()
+      .map(entry -> new GroupOfOccurrences(entry.getKey(), entry.getValue()))
+      .collect(Collectors.toList());
+  }
+
+  public void removeChangeableInputs() {
+    if (this.countChangeableGroups != null) throw new RuntimeException("Changeable already filtered");
+    if (this.groupsOfOccurrences == null) groupByInput();
+    int initialCountOfInputs = this.groupsOfOccurrences.size();
+    this.groupsOfOccurrences.removeIf(GroupOfOccurrences::isChangeable);
+    this.countChangeableGroups = initialCountOfInputs - this.groupsOfOccurrences.size();
+    if (this.countChangeableGroups > 0)
+      LOGGER.info(MessageFormat.format("\tRemoved {0} of {1} changeable inputs from method {2}", this.countChangeableGroups, initialCountOfInputs, this.name));
+  }
+
+  public boolean isChangeable() {
+    if (this.countChangeableGroups != null) return this.countChangeableGroups > 0;
+    if (this.groupsOfOccurrences != null) return this.groupsOfOccurrences.stream().anyMatch(GroupOfOccurrences::isChangeable);
+    Map<String, Object> inputHasOutput = new HashMap<>();
+    for (Occurrence occurrence : this.occurrences) {
+      String parameters = occurrence.getParametersSerialised();
+      if (!inputHasOutput.containsKey(parameters)) inputHasOutput.put(parameters, occurrence.getReturnValue());
+      else if (!Objects.deepEquals(inputHasOutput.get(parameters), occurrence.getReturnValue())) return true;
+    }
+    return false;
+  }
+
+  public void removeSingleOccurrences() {
+    if (this.groupsOfOccurrences == null) groupByInput();
+    int initialCountOfOccurrences = this.groupsOfOccurrences.size();
+    this.groupsOfOccurrences.removeIf(it -> it.occurrences().count() < 2);
+    int removedOccurrences = initialCountOfOccurrences - this.groupsOfOccurrences.size();
+    if (removedOccurrences > 0) LOGGER.info(MessageFormat.format("\tRemoved {0} of {1} inputs from method {2}", removedOccurrences, initialCountOfOccurrences, this.name));
+  }
+
+  public boolean isReusable() {
+    if (this.groupsOfOccurrences != null) return this.groupsOfOccurrences.stream().anyMatch(it -> it.occurrences().count() > 1);
+    Map<String, Long> inputHasFrequency = occurrences()
+      .map(Occurrence::getParametersSerialised)
+      .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
+    return inputHasFrequency.values().stream().anyMatch(frequency -> frequency > 1);
+  }
+
+  public boolean isNotReusable() {
+    return !isReusable();
+  }
+
+  public void recommendTTL() {
+    if (this.pareto != null) LOGGER.warning("metrics already calculated");
+    this.pareto = new Pareto();
+    if (this.occurrences.size() < 2) return;
+    this.occurrences.sort(Comparator.comparingLong(Occurrence::getStartTime));
+    Simulator.simulate(this.occurrences, this.pareto);
+    if (Configuration.getVerbose()) {
+      String application = Configuration.getInput().split(",")[0];
+      this.pareto.values().forEach(it -> System.out.println(application + "," + name + "," + it.getTtl() + "," + it.getSavedTime() + "," + it.getHits() + "," + it.getTimeInCache()));
+    }
+  }
+
+  public void recommendTTLPerInput() {
+    if (this.groupsOfOccurrences == null) {
+      groupByInput();
+      removeSingleOccurrences();
+    }
+    if (Configuration.getVerbose()) {
+      System.out.println("=== " + getName() + " ===");
+      Configuration.setInput(Configuration.getInput().split(",")[0] + "," + getName() + ",");
+      groupsOfOccurrences().forEach(GroupOfOccurrences::calculateMetrics);
+    } else {
+      groupsOfOccurrences().parallel().forEach(GroupOfOccurrences::calculateMetrics);
+    }
+    String uuid = Configuration.getUUID().replace("level:input", "level:method");
+    Pareto pareto = StorageManager.get(uuid, this.occurrences);
+    if (this.pareto == null && pareto != null) this.pareto = pareto;
+  }
+
+  public void removeNotRecommendedInputs() {
+    if (this.groupsOfOccurrences == null) throw new RuntimeException("Recommendations not called");
+    int initialCountOfOccurrences = this.groupsOfOccurrences.size();
+    this.groupsOfOccurrences.removeIf(it -> it.getBestMetrics().getSavedTime() == 0);
+    int removedOccurrences = initialCountOfOccurrences - this.groupsOfOccurrences.size();
+    if (removedOccurrences > 0) LOGGER.info(MessageFormat.format("\tRemoved {0} of {1} inputs from method {2}", removedOccurrences, initialCountOfOccurrences, this.name));
+  }
+
+  public void rankRecommendations() {
+    if (this.groupsOfOccurrences == null) throw new RuntimeException("groupsOfOccurrences is null");
+    this.groupsOfOccurrences.sort((group1, group2) -> Double.compare(group2.getBestMetrics().getSavedTime(), group1.getBestMetrics().getSavedTime()));
+  }
+
+  @Override
+  public String toString() {
+    return this.name;
+  }
 
 }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
index db1120b..d230722 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
@@ -71,11 +71,11 @@ public class Metrics implements Comparable<Metrics> {
         return Math.sqrt(Math.pow(x1 - x2, 2) - Math.pow(y1, y2));
     }
 
-    public double calculateEuclideanDistance(double objectiveSavedTime, double objectiveIdleTime) {
+    protected double calculateEuclideanDistance(double objectiveSavedTime, double objectiveIdleTime) {
         return calculateEuclideanDistance(getSavedTime(), getIdleTime(), objectiveSavedTime, objectiveIdleTime);
     }
 
-    public String getUUID() {
+    protected String getUUID() {
         return uuid;
     }
 
@@ -122,7 +122,7 @@ public class Metrics implements Comparable<Metrics> {
         return Configuration.getPreferences().get(0) * getSavedTime() - Configuration.getPreferences().get(1) * getIdleTime();
     }
 
-    public Metrics getNormalised(double savedTime, double idleTime) {
+    protected Metrics getNormalised(double savedTime, double idleTime) {
         if (savedTime < 0 || savedTime > 1) {
             throw new RuntimeException("wrong savedTime");
         }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Pareto.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Pareto.java
old mode 100644
new mode 100755
index 065a9a7..edfb7da
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Pareto.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Pareto.java
@@ -9,81 +9,83 @@ import java.util.stream.Stream;
 
 public class Pareto {
 
-    private final Map<Double, Metrics> savedTimeHasMetrics;
-
-    public Pareto() {
-        savedTimeHasMetrics = new ConcurrentHashMap<>();
-    }
-
-    public Pareto(List<Metrics> metrics) {
-        this();
-        metrics.forEach(this::addIfPareto);
-    }
-
-    public static void removeDominatedMetrics(Collection<Metrics> allMetrics) {
-        Map<Double, List<Metrics>> groupBySavedTime = allMetrics.stream().collect(Collectors.groupingBy(Metrics::getSavedTime));
-        groupBySavedTime.remove(0L);
-        groupBySavedTime.forEach((savedTime, metrics) -> {
-            double minIdleTime = metrics.stream().mapToDouble(Metrics::getIdleTime).min().orElse(0);
-            metrics.removeIf(metric -> metric.getIdleTime() > minIdleTime);
-        });
-
-        List<Metrics> localMaxima = groupBySavedTime.values().stream()
-                .map(Collection::stream)
-                .reduce(Stream::concat)
-                .orElse(Stream.empty())
-                .collect(Collectors.toList());
-
-        allMetrics.removeIf(metrics -> !localMaxima.contains(metrics));
-    }
-
-    public synchronized void addIfPareto(Metrics metrics) {
-        if (metrics.getSavedTime() == 0) {
-            return;
-        }
-        savedTimeHasMetrics.merge(metrics.getSavedTime(), metrics, (existing, incoming) -> existing.getIdleTime() < incoming.getIdleTime() ? existing : incoming);
+  private final Map<Double, Metrics> savedTimeHasMetrics;
+  private Metrics bestMetrics;
+
+  public Pareto() {
+    savedTimeHasMetrics = new ConcurrentHashMap<>();
+  }
+
+  public Pareto(List<Metrics> metrics) {
+    this();
+    metrics.forEach(this::addIfPareto);
+  }
+
+  public static void removeDominatedMetrics(Collection<Metrics> allMetrics) {
+    Map<Double, List<Metrics>> groupBySavedTime = allMetrics.stream().collect(Collectors.groupingBy(Metrics::getSavedTime));
+    groupBySavedTime.remove(0D);
+    groupBySavedTime.forEach((savedTime, metrics) -> {
+      double minIdleTime = metrics.stream().mapToDouble(Metrics::getIdleTime).min().orElse(0);
+      metrics.removeIf(metric -> metric.getIdleTime() > minIdleTime);
+    });
+
+    List<Metrics> localMaxima = groupBySavedTime.values().stream()
+      .map(Collection::stream)
+      .reduce(Stream::concat)
+      .orElse(Stream.empty())
+      .collect(Collectors.toList());
+
+    allMetrics.removeIf(metrics -> !localMaxima.contains(metrics));
+  }
+
+  public synchronized void addIfPareto(Metrics metrics) {
+    if (metrics.getSavedTime() == 0) {
+      return;
     }
-
-    public Collection<Metrics> values() {
-        return savedTimeHasMetrics.values();
-    }
-
-    public Set<Long> getTtls() {
-        return values().stream().map(Metrics::getTtl).collect(Collectors.toSet());
+    savedTimeHasMetrics.merge(metrics.getSavedTime(), metrics, (existing, incoming) -> existing.getIdleTime() < incoming.getIdleTime() ? existing : incoming);
+  }
+
+  public Collection<Metrics> values() {
+    return savedTimeHasMetrics.values();
+  }
+
+  public Set<Long> getTtls() {
+    return values().stream().map(Metrics::getTtl).collect(Collectors.toSet());
+  }
+
+  public Stream<Metrics> getNormalised() {
+    double minSavedTime = values().stream().mapToDouble(Metrics::getSavedTime).min().orElseThrow();
+    double maxSavedTime = values().stream().mapToDouble(Metrics::getSavedTime).max().orElseThrow();
+    double distanceSavedTime = maxSavedTime - minSavedTime;
+    double minIdleTime = values().stream().mapToDouble(Metrics::getIdleTime).min().orElseThrow();
+    double maxIdleTime = values().stream().mapToDouble(Metrics::getIdleTime).max().orElseThrow();
+    double distanceIdleTime = maxIdleTime - minIdleTime;
+
+    return values().stream().map(it -> {
+      double normalisedSavedTime = (it.getSavedTime() - minSavedTime) / distanceSavedTime;
+      double normalisedIdleTime = (it.getIdleTime() - minIdleTime) / distanceIdleTime;
+      return it.getNormalised(normalisedSavedTime, normalisedIdleTime);
+    });
+  }
+
+  public Metrics getBestMetrics() {
+    if (this.bestMetrics == null) this.bestMetrics = getBestMetrics(Configuration.getPreferences().get(0), Configuration.getPreferences().get(1));
+    return this.bestMetrics;
+  }
+
+  private Metrics getBestMetrics(double percentageObjectiveSavedTime, double percentageObjectiveIdleTime) {
+    if (percentageObjectiveSavedTime < 0 || percentageObjectiveSavedTime > 1) {
+      throw new RuntimeException("invalid objective saved time");
     }
-
-    public Stream<Metrics> getNormalised() {
-        double minSavedTime = values().stream().mapToDouble(Metrics::getSavedTime).min().orElseThrow();
-        double maxSavedTime = values().stream().mapToDouble(Metrics::getSavedTime).max().orElseThrow();
-        double distanceSavedTime = maxSavedTime - minSavedTime;
-        double minIdleTime = values().stream().mapToDouble(Metrics::getIdleTime).min().orElseThrow();
-        double maxIdleTime = values().stream().mapToDouble(Metrics::getIdleTime).max().orElseThrow();
-        double distanceIdleTime = maxIdleTime - minIdleTime;
-
-        return values().stream().map(it -> {
-            double normalisedSavedTime = (it.getSavedTime() - minSavedTime) / distanceSavedTime;
-            double normalisedIdleTime = (it.getIdleTime() - minIdleTime) / distanceIdleTime;
-            return it.getNormalised(normalisedSavedTime, normalisedIdleTime);
-        });
+    if (percentageObjectiveIdleTime < 0 || percentageObjectiveIdleTime > 1) {
+      throw new RuntimeException("invalid objective idle time");
     }
-
-    public Metrics getBestMetrics() {
-        return getBestMetrics(Configuration.getPreferences().get(0), Configuration.getPreferences().get(1));
-    }
-
-    private Metrics getBestMetrics(double percentageObjectiveSavedTime, double percentageObjectiveIdleTime) {
-        if (percentageObjectiveSavedTime < 0 || percentageObjectiveSavedTime > 1) {
-            throw new RuntimeException("invalid objective saved time");
-        }
-        if (percentageObjectiveIdleTime < 0 || percentageObjectiveIdleTime > 1) {
-            throw new RuntimeException("invalid objective idle time");
-        }
-        if (savedTimeHasMetrics.isEmpty()) {
-            return new Metrics();
-        }
-        double minIdleTime = values().stream().mapToDouble(Metrics::getIdleTime).min().orElseThrow() * percentageObjectiveIdleTime;
-        double maxSavedTime = values().stream().mapToDouble(Metrics::getSavedTime).max().orElseThrow() * percentageObjectiveSavedTime;
-        return values().stream().min(Comparator.comparingDouble(it -> it.calculateEuclideanDistance(maxSavedTime, minIdleTime))).orElseThrow();
+    if (savedTimeHasMetrics.isEmpty()) {
+      return new Metrics();
     }
+    double minIdleTime = values().stream().mapToDouble(Metrics::getIdleTime).min().orElseThrow() * percentageObjectiveIdleTime;
+    double maxSavedTime = values().stream().mapToDouble(Metrics::getSavedTime).max().orElseThrow() * percentageObjectiveSavedTime;
+    return values().stream().min(Comparator.comparingDouble(it -> it.calculateEuclideanDistance(maxSavedTime, minIdleTime))).orElseThrow();
+  }
 
 }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
index a17aae1..7744b76 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
@@ -1,8 +1,3 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
 package br.ufrgs.inf.prosoft.tfcache;
 
 import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
@@ -19,175 +14,145 @@ import java.util.stream.Stream;
  */
 public class Simulator {
 
-    private final Pareto pareto;
-
-    public Simulator() {
-        this.pareto = new Pareto();
-    }
-
-    public static void simulate(List<Occurrence> occurrences, Pareto pareto) {
-        Pareto computeIfAbsent = StorageManager.computeIfAbsent(occurrences, () -> {
-            switch (Configuration.getKernel()) {
-                case "exhaustive":
-                    simulate(occurrences, generateAllTTLs(occurrences), pareto);
-                    break;
-                case "optimised":
-                    simulate(occurrences, generateTTLsOfInterest(occurrences), pareto);
-                    break;
-                default:
-                    testKernels(occurrences);
-                    break;
-            }
-            return pareto;
-        });
-        computeIfAbsent.values().forEach(pareto::addIfPareto);
-    }
-
-    public static void simulate(List<Occurrence> occurrences, Stream<Long> ttls, Pareto pareto) {
-        ttls.forEach(actualTTL -> simulate(occurrences.stream(), actualTTL, pareto));
-    }
-
-    public static void simulate(Stream<Occurrence> occurrences, long ttl, Pareto pareto) {
-        Map<String, Long> inputHasCachedTime = new HashMap<>();
-        Map<String, Object> inputHasOutput = new HashMap<>();
-
-        long blindedSavedTime = 0;
-        long realSavedTime = 0;
-        long hits = 0;
-        long computationTime = 0;
-        long timeInCache = 0;
-        long stales = 0;
-
-        Iterator<Occurrence> iterator = occurrences.iterator();
-        while (iterator.hasNext()) {
-            Occurrence occurrence = iterator.next();
-            long adjustedStartTime = occurrence.getStartTime() - blindedSavedTime;
-            long adjustedEndTime = occurrence.getEndTime() - blindedSavedTime;
-            if (occurrence.getExecutionTime() < 0) {
-                throw new RuntimeException("executionTime cannot be under zero");
-            }
-            if (adjustedEndTime < adjustedStartTime) {
-                throw new RuntimeException("adjustedEndTime should not be lesser than adjustedStartTime");
-            }
-            if (inputHasCachedTime.containsKey(occurrence.getParametersSerialised()) &&
-                    adjustedStartTime - inputHasCachedTime.get(occurrence.getParametersSerialised()) > ttl) {
-                inputHasCachedTime.remove(occurrence.getParametersSerialised());
-            }
-            if (inputHasCachedTime.containsKey(occurrence.getParametersSerialised())) {
-                if (Configuration.getStaleness().equals("shrink")) {
-                    if (Objects.deepEquals(inputHasOutput.get(occurrence.getParametersSerialised()), occurrence.getReturnValue())) {
-                        realSavedTime += occurrence.getExecutionTime();
-                    } else {
-                        stales++;
-                    }
-                }
-                blindedSavedTime += occurrence.getExecutionTime();
-                hits++;
-            } else {
-                inputHasCachedTime.put(occurrence.getParametersSerialised(), adjustedEndTime);
-                if (Configuration.getStaleness().equals("shrink")) {
-                    inputHasOutput.put(occurrence.getParametersSerialised(), occurrence.getReturnValue());
-                }
-                computationTime += occurrence.getExecutionTime();
-                timeInCache += ttl;
-            }
+  private final Pareto pareto;
+
+  public Simulator() {
+    this.pareto = new Pareto();
+  }
+
+  public static void simulate(List<Occurrence> occurrences, Pareto pareto) {
+    Pareto computeIfAbsent = StorageManager.computeIfAbsent(occurrences, () -> {
+      if ("exhaustive".equals(Configuration.getKernel())) simulate(occurrences, generateAllTTLs(occurrences), pareto);
+      else if ("optimised".equals(Configuration.getKernel())) simulate(occurrences, generateTTLsOfInterest(occurrences), pareto);
+      else testKernels(occurrences);
+      return pareto;
+    });
+    computeIfAbsent.values().forEach(pareto::addIfPareto);
+  }
+
+  public static void simulate(List<Occurrence> occurrences, Stream<Long> ttls, Pareto pareto) {
+    ttls.forEach(actualTTL -> simulate(occurrences.stream(), actualTTL, pareto));
+  }
+
+  public static void simulate(Stream<Occurrence> occurrences, long ttl, Pareto pareto) {
+    Map<String, Long> inputHasCachedTime = new HashMap<>();
+    Map<String, Object> inputHasOutput = new HashMap<>();
+
+    long blindedSavedTime = 0;
+    long realSavedTime = 0;
+    long hits = 0;
+    long computationTime = 0;
+    long timeInCache = 0;
+    long stales = 0;
+
+    Iterator<Occurrence> iterator = occurrences.iterator();
+    while (iterator.hasNext()) {
+      Occurrence occurrence = iterator.next();
+      long adjustedStartTime = occurrence.getStartTime() - blindedSavedTime;
+      long adjustedEndTime = occurrence.getEndTime() - blindedSavedTime;
+
+      if (occurrence.getExecutionTime() < 0) throw new RuntimeException("executionTime cannot be under zero");
+      if (adjustedEndTime < adjustedStartTime) throw new RuntimeException("adjustedEndTime should not be lesser than adjustedStartTime");
+
+      if (inputHasCachedTime.containsKey(occurrence.getParametersSerialised()) &&
+        adjustedStartTime - inputHasCachedTime.get(occurrence.getParametersSerialised()) > ttl) {
+        inputHasCachedTime.remove(occurrence.getParametersSerialised());
+      }
+      if (inputHasCachedTime.containsKey(occurrence.getParametersSerialised())) {
+        if (Configuration.getStaleness().equals("shrink")) {
+          if (Objects.deepEquals(inputHasOutput.get(occurrence.getParametersSerialised()), occurrence.getReturnValue())) {
+            realSavedTime += occurrence.getExecutionTime();
+          } else {
+            stales++;
+          }
         }
-        Metrics metrics = new Metrics(ttl, hits, timeInCache, computationTime, stales, Configuration.getStaleness().equals("shrink") ? realSavedTime : blindedSavedTime);
-        pareto.addIfPareto(metrics);
+        blindedSavedTime += occurrence.getExecutionTime();
+        hits++;
+      } else {
+        inputHasCachedTime.put(occurrence.getParametersSerialised(), adjustedEndTime);
+        if (Configuration.getStaleness().equals("shrink")) inputHasOutput.put(occurrence.getParametersSerialised(), occurrence.getReturnValue());
+        computationTime += occurrence.getExecutionTime();
+        timeInCache += ttl;
+      }
     }
-
-    public static Stream<Long> generateTTLsOfInterest(Stream<Occurrence> occurrences) {
-        return generateTTLsOfInterest(occurrences.collect(Collectors.toList()));
+    Metrics metrics = new Metrics(ttl, hits, timeInCache, computationTime, stales, Configuration.getStaleness().equals("shrink") ? realSavedTime : blindedSavedTime);
+    pareto.addIfPareto(metrics);
+  }
+
+  public static Stream<Long> generateTTLsOfInterest(List<Occurrence> occurrences) {
+    List<Long> windows = new ArrayList<>();
+    for (int hits = 1; hits < occurrences.size(); hits++) {
+      long window = occurrences.get(hits).getStartTime() - occurrences.get(hits - 1).getEndTime();
+      if (window > 0) windows.add(window);
     }
 
-    public static Stream<Long> generateTTLsOfInterest(List<Occurrence> occurrences) {
-        List<Long> windows = new ArrayList<>();
-        for (int hits = 1; hits < occurrences.size(); hits++) {
-            long window = occurrences.get(hits).getStartTime() - occurrences.get(hits - 1).getEndTime();
-            if (window > 0) {
-                windows.add(window);
-            }
-        }
-
-        Set<Long> ttlsOfInterest = new HashSet<>(windows);
-        for (int hits = 2; hits <= windows.size(); hits++) {
-            for (int shift = 0; shift <= windows.size() - hits; shift++) {
-                long ttl = 0;
-                for (int k = shift; k < shift + hits; k++) {
-                    ttl += windows.get(k);
-                }
-                ttlsOfInterest.add(ttl);
-            }
-        }
-        return ttlsOfInterest.stream().parallel();
+    Set<Long> ttlsOfInterest = new HashSet<>(windows);
+    for (int hits = 2; hits <= windows.size(); hits++) {
+      for (int shift = 0; shift <= windows.size() - hits; shift++) {
+        long ttl = 0;
+        for (int k = shift; k < shift + hits; k++) ttl += windows.get(k);
+        ttlsOfInterest.add(ttl);
+      }
     }
-
-    public static Stream<Long> generateAllTTLs(List<Occurrence> occurrences) {
-        long maxTTL = occurrences.get(occurrences.size() - 1).getStartTime() - occurrences.get(0).getEndTime();
-        long minTTL = Long.MAX_VALUE;
-        for (int i = 0; i < occurrences.size() - 1; i++) {
-            long ttl = occurrences.get(i + 1).getStartTime() - occurrences.get(i).getEndTime();
-            if (ttl > 0 && ttl < minTTL) {
-                minTTL = ttl;
-            }
-        }
-        return LongStream.rangeClosed(minTTL, maxTTL).boxed().parallel();
+    return ttlsOfInterest.stream().parallel();
+  }
+
+  public static Stream<Long> generateAllTTLs(List<Occurrence> occurrences) {
+    long maxTTL = occurrences.get(occurrences.size() - 1).getStartTime() - occurrences.get(0).getEndTime();
+    long minTTL = Long.MAX_VALUE;
+    for (int i = 0; i < occurrences.size() - 1; i++) {
+      long ttl = occurrences.get(i + 1).getStartTime() - occurrences.get(i).getEndTime();
+      if (ttl > 0 && ttl < minTTL) minTTL = ttl;
     }
-
-    private static void testKernels(List<Occurrence> occurrences) {
-        Pareto optimisedPareto = new Pareto();
-        Pareto exhaustivePareto = new Pareto();
-
-        Map<String, List<Occurrence>> inputHasOccurrences = Method.groupByInput(occurrences);
-        Set<Long> ttlsOfInterest = inputHasOccurrences.values().stream()
-                .map(Simulator::generateTTLsOfInterest)
-                .reduce(Stream::concat)
-                .orElse(Stream.empty())
-                .collect(Collectors.toSet());
-
-        simulate(occurrences, ttlsOfInterest.stream(), optimisedPareto);
-        simulate(occurrences, generateAllTTLs(occurrences), exhaustivePareto);
-
-        List<Long> missingTTLs = exhaustivePareto.values().stream().map(Metrics::getTtl)
-                .filter(ttl -> !ttlsOfInterest.contains(ttl))
-                .sorted()
-                .collect(Collectors.toList());
-        if (!missingTTLs.isEmpty()) {
-            System.out.println("=== " + Configuration.getInput() + " ===");
-            System.out.println("\tMissing ttls: " + missingTTLs);
-        }
-
-        Metrics maxExhaustiveMetrics = exhaustivePareto.getBestMetrics();
-        Metrics maxOptimisedMetrics = optimisedPareto.getBestMetrics();
-        if (maxExhaustiveMetrics.getTtl() != maxOptimisedMetrics.getTtl()) {
-            System.out.println("=== " + Configuration.getInput() + " ===");
-            System.out.println("\tDIFFERENT BEST METRICS");
-            System.out.println("\tOptimised: " + maxOptimisedMetrics);
-            System.out.println("\tExhaustive: " + maxExhaustiveMetrics);
-            switch (maxExhaustiveMetrics.compareTo(maxOptimisedMetrics)) {
-                case -1:
-                    System.out.println("\tOptimised won");
-                    break;
-                case 1:
-                    System.out.println("\tExhaustive won");
-                    break;
-                default:
-                    System.out.println("\tEquivalent recommendation");
-                    break;
-            }
-        }
+    return LongStream.rangeClosed(minTTL, maxTTL).boxed().parallel();
+  }
+
+  private static void testKernels(List<Occurrence> occurrences) {
+    Pareto optimisedPareto = new Pareto();
+    Pareto exhaustivePareto = new Pareto();
+
+    Map<String, List<Occurrence>> inputHasOccurrences = Method.groupByInput(occurrences);
+    Set<Long> ttlsOfInterest = inputHasOccurrences.values().stream()
+      .map(Simulator::generateTTLsOfInterest)
+      .reduce(Stream::concat)
+      .orElse(Stream.empty())
+      .collect(Collectors.toSet());
+
+    simulate(occurrences, ttlsOfInterest.stream(), optimisedPareto);
+    simulate(occurrences, generateAllTTLs(occurrences), exhaustivePareto);
+
+    List<Long> missingTTLs = exhaustivePareto.values().stream().map(Metrics::getTtl)
+      .filter(ttl -> !ttlsOfInterest.contains(ttl))
+      .sorted()
+      .collect(Collectors.toList());
+    if (!missingTTLs.isEmpty()) {
+      System.out.println("=== " + Configuration.getInput() + " ===");
+      System.out.println("\tMissing ttls: " + missingTTLs);
     }
 
-    public Pareto getPareto() {
-        return pareto;
+    Metrics maxExhaustiveMetrics = exhaustivePareto.getBestMetrics();
+    Metrics maxOptimisedMetrics = optimisedPareto.getBestMetrics();
+    if (maxExhaustiveMetrics.getTtl() != maxOptimisedMetrics.getTtl()) {
+      System.out.println("=== " + Configuration.getInput() + " ===");
+      System.out.println("\tDIFFERENT BEST METRICS");
+      System.out.println("\tOptimised: " + maxOptimisedMetrics);
+      System.out.println("\tExhaustive: " + maxExhaustiveMetrics);
+      if (maxExhaustiveMetrics.compareTo(maxOptimisedMetrics) < 0) System.out.println("\tOptimised won");
+      else if (maxExhaustiveMetrics.compareTo(maxOptimisedMetrics) > 0) System.out.println("\tExhaustive won");
+      else System.out.println("\tEquivalent recommendation");
     }
+  }
 
-    public void simulate(List<Occurrence> occurrences) {
-        simulate(occurrences, this.pareto);
-    }
+  public Pareto getPareto() {
+    return pareto;
+  }
 
-    public void simulate(Stream<Occurrence> occurrences, long ttl) {
-        simulate(occurrences, ttl, this.pareto);
-    }
+  public void simulate(List<Occurrence> occurrences) {
+    simulate(occurrences, this.pareto);
+  }
+
+  public void simulate(Stream<Occurrence> occurrences, long ttl) {
+    simulate(occurrences, ttl, this.pareto);
+  }
 
 }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java
index 19873f7..22238de 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java
@@ -1,8 +1,3 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
 package br.ufrgs.inf.prosoft.tfcache;
 
 import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
@@ -19,122 +14,108 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.Stream;
 
-/**
- * @author romulo
- */
 public class TFCache {
 
-    private static final Logger LOGGER = Logger.getLogger(TFCache.class.getName());
-    private final List<Method> methods;
+  private static final Logger LOGGER = Logger.getLogger(TFCache.class.getName());
+  private final List<Method> methods;
 
-    public TFCache(List<Method> methods) {
-        this.methods = methods;
-    }
+  public TFCache(List<Method> methods) {
+    this.methods = methods;
+  }
 
-    public Stream<Method> methods() {
-        return this.methods.stream();
-    }
+  public Stream<Method> methods() {
+    return this.methods.stream();
+  }
 
-    private void recommendCacheableMethods() {
-        if (Configuration.getChangeability().equals("deny")) {
-            LOGGER.log(Level.INFO, "Removing changeable from {0} methods", this.methods.size());
-            this.methods.removeIf(Method::isChangeable);
-        }
-        LOGGER.log(Level.INFO, "Removing not reusable from {0} methods", this.methods.size());
-        this.methods.removeIf(Method::isNotReusable);
-        LOGGER.log(Level.INFO, "Recommending TTL to {0} methods", this.methods.size());
-        if (Configuration.getVerbose()) {
-            this.methods.forEach(Method::recommendTTL);
-        } else {
-            this.methods.stream().parallel().forEach(Method::recommendTTL);
-        }
-        LOGGER.log(Level.INFO, "Removing not recommended from {0}", this.methods.size());
-        this.methods.removeIf(method -> method.getBestMetrics().getSavedTime() == 0);
-        LOGGER.log(Level.INFO, "Ranking {0} methods according saved time", this.methods.size());
-        this.methods.sort((method1, method2) -> Double.compare(method2.getBestMetrics().getSavedTime(), method1.getBestMetrics().getSavedTime()));
-        LOGGER.log(Level.INFO, "Printing recommendations for {0} methods", this.methods.size());
-        this.methods.forEach(method -> System.out.println(method.getName()
-                + " Occurrences " + method.getOccurrencesSize()
-                + " Inputs " + method.groupsOfOccurrences().count()
-                + " TTL " + method.getBestMetrics().getTtl()
-                + " Saves " + method.getBestMetrics().getSavedTime()
-                + " Hits " + method.getBestMetrics().getHits()
-                + " Computation " + method.getBestMetrics().getComputationTime()
-                + " TimeInCache " + method.getBestMetrics().getTimeInCache()
-                + " Stales " + method.getBestMetrics().getStales()));
+  private void recommendCacheableMethods() {
+    if (Configuration.getChangeability().equals("deny")) {
+      LOGGER.log(Level.INFO, "Removing changeable from {0} methods", this.methods.size());
+      this.methods.removeIf(Method::isChangeable);
     }
+    LOGGER.log(Level.INFO, "Removing not reusable from {0} methods", this.methods.size());
+    this.methods.removeIf(Method::isNotReusable);
+    LOGGER.log(Level.INFO, "Recommending TTL to {0} methods", this.methods.size());
+    if (Configuration.getVerbose()) this.methods.forEach(Method::recommendTTL);
+    else this.methods.stream().parallel().forEach(Method::recommendTTL);
+    LOGGER.log(Level.INFO, "Removing not recommended from {0}", this.methods.size());
+    this.methods.removeIf(method -> method.getBestMetrics().getSavedTime() == 0);
+    LOGGER.log(Level.INFO, "Ranking {0} methods according saved time", this.methods.size());
+    this.methods.sort((method1, method2) -> Double.compare(method2.getBestMetrics().getSavedTime(), method1.getBestMetrics().getSavedTime()));
+    LOGGER.log(Level.INFO, "Printing recommendations for {0} methods", this.methods.size());
+    this.methods.forEach(method -> System.out.println(method.getName()
+      + " Occurrences " + method.occurrences().count()
+      + " Inputs " + method.groupsOfOccurrences().count()
+      + " TTL " + method.getBestMetrics().getTtl()
+      + " Saves " + method.getBestMetrics().getSavedTime()
+      + " Hits " + method.getBestMetrics().getHits()
+      + " Computation " + method.getBestMetrics().getComputationTime()
+      + " TimeInCache " + method.getBestMetrics().getTimeInCache()
+      + " Stales " + method.getBestMetrics().getStales()));
+  }
 
-    private void recommendCacheableInputs() {
-        if (Configuration.getChangeability().equals("deny")) {
-            LOGGER.log(Level.INFO, "Removing changeable inputs from {0} methods", this.methods.size());
-            this.methods.forEach(Method::removeChangeableInputs);
-        }
-        LOGGER.log(Level.INFO, "Removing not reusable inputs from {0} methods", this.methods.size());
-        this.methods.forEach(Method::removeSingleOccurrences);
-        LOGGER.log(Level.INFO, "Removing not reusable methods from {0} methods", this.methods.size());
-        this.methods.removeIf(method -> method.groupsOfOccurrences().count() < 1);
-        LOGGER.log(Level.INFO, "Recommending TTL to {0} methods", this.methods.size());
-        if (Configuration.getVerbose()) {
-            this.methods.forEach(Method::recommendTTLPerInput);
-        } else {
-            this.methods.stream().parallel().forEach(Method::recommendTTLPerInput);
-        }
-        LOGGER.log(Level.INFO, "Removing not recommended inputs from {0}", this.methods.size());
-        this.methods.forEach(Method::removeNotRecommendedInputs);
-        LOGGER.log(Level.INFO, "Removing not recommended methods from {0}", this.methods.size());
-        this.methods.removeIf(method -> method.groupsOfOccurrences().count() < 1);
-        LOGGER.log(Level.INFO, "Ranking {0} methods and inputs according saved time", this.methods.size());
-        this.methods.forEach(Method::rankRecommendations);
-        this.methods.sort((method1, method2) -> Double.compare(method2.getEstimatedSavedTime(), method1.getEstimatedSavedTime()));
-        LOGGER.log(Level.INFO, "Printing recommendations for {0} methods", this.methods.size());
-        this.methods.forEach(method -> {
-            if (method.getBestMetrics() != null) {
-                System.out.println(method.getName()
-                        + " Occurrences " + method.getOccurrencesSize()
-                        + " Inputs " + method.groupsOfOccurrences().count()
-                        + " TTL " + method.getBestMetrics().getTtl()
-                        + " Saves " + method.getBestMetrics().getSavedTime()
-                        + " Hits " + method.getBestMetrics().getHits()
-                        + " Computation " + method.getBestMetrics().getComputationTime()
-                        + " TimeInCache " + method.getBestMetrics().getTimeInCache()
-                        + " Stales " + method.getBestMetrics().getStales());
-            } else {
-                System.out.println(method.getName());
-            }
-            if (Configuration.getVerbose()) {
-                method.groupsOfOccurrences().forEach(group -> System.out.println("\t" + group.getParameters().hashCode()
-                        + " Occurrences " + group.getOccurrencesSize()
-                        + " -> TTL " + group.getTtl()
-                        + " Saves " + group.getSavedTime()
-                        + " Hits " + group.getBestMetrics().getHits()
-                        + " Computation " + group.getBestMetrics().getComputationTime()
-                        + " TimeInCache " + group.getBestMetrics().getTimeInCache()
-                        + " Stales " + group.getBestMetrics().getHits()));
-            }
-        });
-        try (FileWriter fileWriter = new FileWriter(Configuration.getOutput())) {
-            JsonObject jsonCacheableParameters = new JsonObject();
-            this.methods.forEach(method -> {
-                JsonObject cacheableParameters = new JsonObject();
-                method.groupsOfOccurrences().forEach(group -> cacheableParameters.addProperty(group.getParameters(), group.getTtl()));
-                jsonCacheableParameters.add(method.getName(), cacheableParameters);
-            });
-            Gson gson = new GsonBuilder().setPrettyPrinting().create();
-            gson.toJson(jsonCacheableParameters, fileWriter);
-        } catch (IOException ex) {
-            LOGGER.log(Level.SEVERE, "invalid <outputPath>");
-        }
+  private void recommendCacheableInputs() {
+    if (Configuration.getChangeability().equals("deny")) {
+      LOGGER.log(Level.INFO, "Removing changeable inputs from {0} methods", this.methods.size());
+      this.methods.forEach(Method::removeChangeableInputs);
     }
-
-    public void recommend() {
-        if (Configuration.getVerbose()) {
-            Configuration.setInput(Arrays.stream(Configuration.getInput().split("/")).reduce((a, b) -> b).orElse(""));
-        }
-        if (Configuration.getLevel().equals("method")) {
-            recommendCacheableMethods();
-        } else {
-            recommendCacheableInputs();
-        }
+    LOGGER.log(Level.INFO, "Removing not reusable inputs from {0} methods", this.methods.size());
+    this.methods.forEach(Method::removeSingleOccurrences);
+    LOGGER.log(Level.INFO, "Removing not reusable methods from {0} methods", this.methods.size());
+    this.methods.removeIf(method -> method.groupsOfOccurrences().count() < 1);
+    LOGGER.log(Level.INFO, "Recommending TTL to {0} methods", this.methods.size());
+    if (Configuration.getVerbose()) this.methods.forEach(Method::recommendTTLPerInput);
+    else this.methods.stream().parallel().forEach(Method::recommendTTLPerInput);
+    LOGGER.log(Level.INFO, "Removing not recommended inputs from {0}", this.methods.size());
+    this.methods.forEach(Method::removeNotRecommendedInputs);
+    LOGGER.log(Level.INFO, "Removing not recommended methods from {0}", this.methods.size());
+    this.methods.removeIf(method -> method.groupsOfOccurrences().count() < 1);
+    LOGGER.log(Level.INFO, "Ranking {0} methods and inputs according saved time", this.methods.size());
+    this.methods.forEach(Method::rankRecommendations);
+    this.methods.sort((method1, method2) -> Double.compare(method2.getEstimatedSavedTime(), method1.getEstimatedSavedTime()));
+    LOGGER.log(Level.INFO, "Printing recommendations for {0} methods", this.methods.size());
+    this.methods.forEach(method -> {
+      if (method.getBestMetrics() != null) {
+        System.out.println(method.getName()
+          + " Occurrences " + method.occurrences().count()
+          + " Inputs " + method.groupsOfOccurrences().count()
+          + " TTL " + method.getBestMetrics().getTtl()
+          + " Saves " + method.getBestMetrics().getSavedTime()
+          + " Hits " + method.getBestMetrics().getHits()
+          + " Computation " + method.getBestMetrics().getComputationTime()
+          + " TimeInCache " + method.getBestMetrics().getTimeInCache()
+          + " Stales " + method.getBestMetrics().getStales());
+      } else {
+        System.out.println(method.getName());
+      }
+      if (Configuration.getVerbose()) {
+        method.groupsOfOccurrences().forEach(group -> System.out.println("\t" + group.getParameters().hashCode()
+          + " Occurrences " + group.occurrences().count()
+          + " -> TTL " + group.getBestMetrics().getTtl()
+          + " Saves " + group.getBestMetrics().getSavedTime()
+          + " Hits " + group.getBestMetrics().getHits()
+          + " Computation " + group.getBestMetrics().getComputationTime()
+          + " TimeInCache " + group.getBestMetrics().getTimeInCache()
+          + " Stales " + group.getBestMetrics().getHits()));
+      }
+    });
+    try (FileWriter fileWriter = new FileWriter(Configuration.getOutput())) {
+      JsonObject jsonCacheableParameters = new JsonObject();
+      this.methods.forEach(method -> {
+        JsonObject cacheableParameters = new JsonObject();
+        method.groupsOfOccurrences().forEach(it -> cacheableParameters.addProperty(it.getParameters(), it.getBestMetrics().getTtl()));
+        jsonCacheableParameters.add(method.getName(), cacheableParameters);
+      });
+      Gson gson = new GsonBuilder().setPrettyPrinting().create();
+      gson.toJson(jsonCacheableParameters, fileWriter);
+    } catch (IOException ex) {
+      LOGGER.log(Level.SEVERE, "invalid <outputPath>");
     }
+  }
+
+  public void recommend() {
+    if (Configuration.getVerbose()) Configuration.setInput(Arrays.stream(Configuration.getInput().split("/")).reduce((a, b) -> b).orElse(""));
+    if (Configuration.getLevel().equals("method")) recommendCacheableMethods();
+    else recommendCacheableInputs();
+  }
 
 }