cache
Changes
src/main/java/br/ufrgs/inf/prosoft/cache/Caffeine.java 376(+182 -194)
src/main/java/br/ufrgs/inf/prosoft/cache/tools/CSV.java 415(+188 -227)
Details
diff --git a/src/main/java/br/ufrgs/inf/prosoft/cache/Cache.java b/src/main/java/br/ufrgs/inf/prosoft/cache/Cache.java
index f71673a..ea8de42 100644
--- a/src/main/java/br/ufrgs/inf/prosoft/cache/Cache.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/cache/Cache.java
@@ -9,42 +9,41 @@ import java.util.Optional;
import java.util.function.Supplier;
/**
- *
- * @author romulo
* @param <K>
* @param <V>
+ * @author romulo
*/
public interface Cache<K, V> {
- public void put(K key, V value, long timeToLive);
+ void put(K key, V value, long timeToLive);
- public void put(K key, V value);
+ void put(K key, V value);
- public V get(K key) throws KeyNotFoundException;
+ V get(K key) throws KeyNotFoundException;
- public void invalidate(K key);
+ void invalidate(K key);
- public default V computeIfAbsent(K key, Supplier<V> supplier, long timeToLive) {
- synchronized (Optional.ofNullable(key)) {
- try {
- return get(key);
- } catch (KeyNotFoundException ex) {
- V get = supplier.get();
- put(key, get, timeToLive);
- return get;
- }
- }
+ default V computeIfAbsent(K key, Supplier<V> supplier, long timeToLive) {
+ synchronized (Optional.ofNullable(key)) {
+ try {
+ return get(key);
+ } catch (KeyNotFoundException ex) {
+ V get = supplier.get();
+ put(key, get, timeToLive);
+ return get;
+ }
}
-
- public default V computeIfAbsent(K key, Supplier<V> supplier) {
- synchronized (Optional.ofNullable(key)) {
- try {
- return get(key);
- } catch (KeyNotFoundException ex) {
- V get = supplier.get();
- put(key, get);
- return get;
- }
- }
+ }
+
+ default V computeIfAbsent(K key, Supplier<V> supplier) {
+ synchronized (Optional.ofNullable(key)) {
+ try {
+ return get(key);
+ } catch (KeyNotFoundException ex) {
+ V get = supplier.get();
+ put(key, get);
+ return get;
+ }
}
+ }
}
src/main/java/br/ufrgs/inf/prosoft/cache/Caffeine.java 376(+182 -194)
diff --git a/src/main/java/br/ufrgs/inf/prosoft/cache/Caffeine.java b/src/main/java/br/ufrgs/inf/prosoft/cache/Caffeine.java
index 974e524..2612978 100644
--- a/src/main/java/br/ufrgs/inf/prosoft/cache/Caffeine.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/cache/Caffeine.java
@@ -1,212 +1,200 @@
-/*
- * 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.cache;
+import br.ufrgs.inf.prosoft.cache.tools.CSV;
import com.github.benmanes.caffeine.cache.Expiry;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
+
+import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
-/**
- *
- * @author root
- * @param <K>
- * @param <V>
- */
public class Caffeine<K, V> implements Cache<K, V>, AutoCloseable {
- private static final boolean CACHE_EVALUATE_PERFORMANCE = System.getenv("CACHE_EVENTS") == null || !System.getenv("CACHE_EVENTS").equals("false");
- private static final boolean CACHE_REGISTER_SIZE = System.getenv("CACHE_REGISTER_SIZE") != null && System.getenv("CACHE_REGISTER_SIZE").equals("true");
- private static final boolean CACHE_REFRESH_TTL = System.getenv("CACHE_REFRESH_TTL") != null && System.getenv("CACHE_REFRESH_TTL").equals("true");
- private final com.github.benmanes.caffeine.cache.Cache<Optional<K>, Optional<V>> cache;
- private final ConcurrentHashMap<Optional<K>, Long> keyHasTTL;
- private final CachePerformance cachePerformance;
- private final ScheduledExecutorService sizeMonitor;
-
- public Caffeine() {
- this(new CachePerformance());
- }
-
- public Caffeine(long ttl) {
- this(new CachePerformance(), ttl);
- }
-
- public Caffeine(String name) {
- this(new CachePerformance(name));
- }
-
- public Caffeine(String name, long ttl) {
- this(new CachePerformance(name), ttl);
- }
-
- public Caffeine(String name, Long ttl, long size) {
- this(new CachePerformance(name), ttl, size);
- }
-
- public Caffeine(CachePerformance cachingPerformance) {
- this(cachingPerformance, null);
- }
-
- public Caffeine(CachePerformance cachingPerformance, Long defaultTTL) {
- this(cachingPerformance, defaultTTL, null);
- }
-
- public Caffeine(CachePerformance cachingPerformance, Long defaultTTL, Long size) {
- this.cachePerformance = cachingPerformance;
- this.keyHasTTL = new ConcurrentHashMap<>();
- com.github.benmanes.caffeine.cache.Caffeine<Optional<K>, Optional<V>> builder = com.github.benmanes.caffeine.cache.Caffeine.newBuilder()
- .expireAfter(new Expiry<Optional<K>, Optional<V>>() {
- @Override
- public long expireAfterCreate(Optional<K> key, Optional<V> value, long currentTime) {
- Long customTTL = keyHasTTL.remove(key);
- if (customTTL != null) {
- return customTTL * 1000000;
- }
- if (defaultTTL == null) {
- return Long.MAX_VALUE;
- }
- long ttlNanoseconds = defaultTTL * 1000000;
- if (ttlNanoseconds < 0) {
- return Long.MAX_VALUE;
- }
- return ttlNanoseconds;
- }
-
- @Override
- public long expireAfterUpdate(Optional<K> key, Optional<V> value, long currentTime, long currentDuration) {
- return expireAfterCreate(key, value, currentTime);
- }
-
- @Override
- public long expireAfterRead(Optional<K> key, Optional<V> value, long currentTime, long currentDuration) {
- if (CACHE_REFRESH_TTL) {
- return expireAfterCreate(key, value, currentTime);
- }
- return currentDuration;
- }
- });
- if (size != null) {
- builder.maximumSize(size);
- }
- builder.removalListener((k, v, cause) -> {
- registerEvent(EventType.INVALIDATION, v.orElse(null));
- });
- this.cache = builder.build();
-
- this.sizeMonitor = Executors.newScheduledThreadPool(0);
- if (Caffeine.CACHE_EVALUATE_PERFORMANCE) {
- this.sizeMonitor.scheduleAtFixedRate(() -> {
- this.cachePerformance.registerEvent(EventType.POPULATION, String.valueOf(size()));
- }, 0, 5, TimeUnit.SECONDS);
- }
- }
-
- @Override
- public void close() throws IOException {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException ex) {
- }
- this.sizeMonitor.shutdown();
- }
-
- public CachePerformance getCachePerformance() {
- return this.cachePerformance;
- }
-
- @Override
- public void put(K key, V value, long timeToLive) {
- this.keyHasTTL.put(Optional.ofNullable(key), timeToLive);
- put(key, value);
- }
-
- @Override
- public void put(K key, V value) {
- this.cache.put(Optional.ofNullable(key), Optional.ofNullable(value));
- registerEvent(EventType.ADDITION, value);
- }
-
- @Override
- public V get(K key) throws KeyNotFoundException {
- Optional<V> get = this.cache.getIfPresent(Optional.ofNullable(key));
- if (get == null) {
- registerEvent(EventType.MISS, key);
- throw new KeyNotFoundException();
+ private static final Logger LOGGER = Logger.getLogger(Caffeine.class.getName());
+ private static final boolean CACHE_EVALUATE_PERFORMANCE = System.getenv("CACHE_EVENTS") == null || !System.getenv("CACHE_EVENTS").equals("false");
+ private static final boolean CACHE_REGISTER_SIZE = System.getenv("CACHE_REGISTER_SIZE") != null && System.getenv("CACHE_REGISTER_SIZE").equals("true");
+ private static final boolean CACHE_REFRESH_TTL = System.getenv("CACHE_REFRESH_TTL") != null && System.getenv("CACHE_REFRESH_TTL").equals("true");
+ private static final String CACHE_METHODS_TTL = System.getenv("CACHE_METHODS_TTL") != null && !System.getenv("CACHE_METHODS_TTL").isEmpty() && !System.getenv("CACHE_METHODS_TTL").equals("null")
+ ? System.getenv("CACHE_METHODS_TTL") : null;
+ private static final Map<String, Long> METHOD_HAS_TTL = new HashMap<>();
+ private final com.github.benmanes.caffeine.cache.Cache<Optional<K>, Optional<V>> cache;
+ private final ConcurrentHashMap<Optional<K>, Long> keyHasTTL;
+ private final CachePerformance cachePerformance;
+ private final ScheduledExecutorService sizeMonitor;
+
+ public Caffeine() {
+ this(new CachePerformance());
+ }
+
+ public Caffeine(long ttl) {
+ this(new CachePerformance(), ttl);
+ }
+
+ public Caffeine(String name) {
+ this(new CachePerformance(name));
+ }
+
+ public Caffeine(String name, long ttl) {
+ this(new CachePerformance(name), ttl);
+ }
+
+ public Caffeine(String name, Long ttl, long size) {
+ this(new CachePerformance(name), ttl, size);
+ }
+
+ public Caffeine(CachePerformance cachingPerformance) {
+ this(cachingPerformance, null);
+ }
+
+ public Caffeine(CachePerformance cachingPerformance, Long defaultTTL) {
+ this(cachingPerformance, defaultTTL, null);
+ }
+
+ public Caffeine(CachePerformance cachingPerformance, Long defaultTTL, Long size) {
+ this.cachePerformance = cachingPerformance;
+ this.keyHasTTL = new ConcurrentHashMap<>();
+ com.github.benmanes.caffeine.cache.Caffeine<Optional<K>, Optional<V>> builder = com.github.benmanes.caffeine.cache.Caffeine.newBuilder()
+ .expireAfter(new Expiry<Optional<K>, Optional<V>>() {
+ @Override
+ public long expireAfterCreate(Optional<K> key, Optional<V> value, long currentTime) {
+ Long customTTL = keyHasTTL.remove(key);
+ if (customTTL != null) return customTTL * 1000000;
+ if (defaultTTL == null) return Long.MAX_VALUE;
+ long ttlNanoseconds = defaultTTL * 1000000;
+ if (ttlNanoseconds < 0) return Long.MAX_VALUE;
+ return ttlNanoseconds;
}
- registerEvent(EventType.HIT, get);
- return get.orElse(null);
- }
- @Override
- public void invalidate(K key) {
- this.cache.invalidate(Optional.ofNullable(key));
- registerEvent(EventType.INVALIDATION, null);
- }
-
- public boolean containsKey(K key) {
- Optional<V> get = this.cache.getIfPresent(Optional.ofNullable(key));
- return get != null;
- }
-
- public Set<Map.Entry<K, V>> entrySet() {
- Set<Map.Entry<K, V>> entrySet = this.cache.asMap().entrySet().stream().collect(
- Collectors.toMap(entry -> entry.getKey().orElse(null), entry -> entry.getValue().orElse(null)))
- .entrySet();
- if (CACHE_EVALUATE_PERFORMANCE) {
- entrySet.forEach(entry -> {
- registerEvent(EventType.HIT, entry.getValue());
- });
+ @Override
+ public long expireAfterUpdate(Optional<K> key, Optional<V> value, long currentTime, long currentDuration) {
+ return expireAfterCreate(key, value, currentTime);
}
- return entrySet;
- }
-
- public void forEach(BiConsumer<K, V> consumer) {
- entrySet().forEach(entry -> {
- consumer.accept(entry.getKey(), entry.getValue());
- });
- }
-
- public int size() {
- return this.cache.asMap().size();
- }
-
- public boolean isEmpty() {
- return this.cache.asMap().isEmpty();
- }
-
- private String getIdentifier(Object object) {
- return object != null ? String.valueOf(object.hashCode()) : "null";
- }
- private void registerEvent(EventType eventType, Object object) {
- if (!CACHE_EVALUATE_PERFORMANCE) {
- return;
- }
- String identifier = getIdentifier(object);
- if (CACHE_REGISTER_SIZE) {
- this.cachePerformance.registerEvent(eventType, identifier, CachePerformance.calculateObjectSize(object));
- } else {
- this.cachePerformance.registerEvent(eventType, identifier);
- }
- if (eventType.equals(EventType.ADDITION)) {
- this.cachePerformance.registerSize(size());
+ @Override
+ public long expireAfterRead(Optional<K> key, Optional<V> value, long currentTime, long currentDuration) {
+ if (CACHE_REFRESH_TTL) return expireAfterCreate(key, value, currentTime);
+ return currentDuration;
}
- }
-
- public List<V> values() {
- return this.cache.asMap().values().stream()
- .map(value -> value.orElse(null))
- .collect(Collectors.toList());
- }
+ });
+ if (size != null) builder.maximumSize(size);
+ builder.removalListener((k, v, cause) -> registerEvent(EventType.INVALIDATION, v.orElse(null)));
+ this.cache = builder.build();
+
+ this.sizeMonitor = Executors.newScheduledThreadPool(0);
+ if (Caffeine.CACHE_EVALUATE_PERFORMANCE) this.sizeMonitor.scheduleAtFixedRate(() ->
+ this.cachePerformance.registerEvent(EventType.POPULATION, String.valueOf(size())), 0, 5, TimeUnit.SECONDS);
+ }
+
+ private synchronized static void loadCacheableParameters() {
+ if (CACHE_METHODS_TTL == null) return;
+ if (METHOD_HAS_TTL.isEmpty()) return;
+ CSV methodsHasTTL = CSV.read(CACHE_METHODS_TTL);
+ methodsHasTTL.stream().forEach(it -> METHOD_HAS_TTL.put(it.get("method"), Long.valueOf(it.get("ttl"))));
+ }
+
+ @Override
+ public void close() {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ignored) {
+ }
+ this.sizeMonitor.shutdown();
+ }
+
+ public CachePerformance getCachePerformance() {
+ return this.cachePerformance;
+ }
+
+ private Long getTTLForMethod() {
+ loadCacheableParameters();
+ if (METHOD_HAS_TTL.isEmpty()) return null;
+ StackTraceElement[] traceElements = Thread.currentThread().getStackTrace();
+ for (int i = 3; i < traceElements.length; i++) {
+ String methodName = traceElements[i].getClassName() + "." + traceElements[i].getMethodName();
+ if (METHOD_HAS_TTL.containsKey(methodName)) return METHOD_HAS_TTL.get(methodName);
+ }
+ return null;
+ }
+
+ @Override
+ public void put(K key, V value, long timeToLive) {
+ Long ttl = getTTLForMethod();
+ if (ttl != null) timeToLive = ttl;
+ this.keyHasTTL.put(Optional.ofNullable(key), timeToLive);
+ put(key, value);
+ }
+
+ @Override
+ public void put(K key, V value) {
+ if (!this.keyHasTTL.containsKey(Optional.ofNullable(key))) {
+ Long ttl = getTTLForMethod();
+ if (ttl != null) this.keyHasTTL.put(Optional.ofNullable(key), ttl);
+ }
+ this.cache.put(Optional.ofNullable(key), Optional.ofNullable(value));
+ registerEvent(EventType.ADDITION, value);
+ }
+
+ @Override
+ public V get(K key) throws KeyNotFoundException {
+ Optional<V> get = this.cache.getIfPresent(Optional.ofNullable(key));
+ if (get == null) {
+ registerEvent(EventType.MISS, key);
+ throw new KeyNotFoundException();
+ }
+ registerEvent(EventType.HIT, get);
+ return get.orElse(null);
+ }
+
+ @Override
+ public void invalidate(K key) {
+ this.cache.invalidate(Optional.ofNullable(key));
+ registerEvent(EventType.INVALIDATION, null);
+ }
+
+ public boolean containsKey(K key) {
+ Optional<V> get = this.cache.getIfPresent(Optional.ofNullable(key));
+ return get != null;
+ }
+
+ public Set<Map.Entry<K, V>> entrySet() {
+ Set<Map.Entry<K, V>> entrySet = this.cache.asMap().entrySet().stream().collect(
+ Collectors.toMap(entry -> entry.getKey().orElse(null), entry -> entry.getValue().orElse(null))
+ ).entrySet();
+ if (CACHE_EVALUATE_PERFORMANCE) entrySet.forEach(entry -> registerEvent(EventType.HIT, entry.getValue()));
+ return entrySet;
+ }
+
+ public void forEach(BiConsumer<K, V> consumer) {
+ entrySet().forEach(entry -> consumer.accept(entry.getKey(), entry.getValue()));
+ }
+
+ public int size() {
+ return this.cache.asMap().size();
+ }
+
+ public boolean isEmpty() {
+ return this.cache.asMap().isEmpty();
+ }
+
+ private String getIdentifier(Object object) {
+ return object != null ? String.valueOf(object.hashCode()) : "null";
+ }
+
+ private void registerEvent(EventType eventType, Object object) {
+ if (!CACHE_EVALUATE_PERFORMANCE) return;
+ String identifier = getIdentifier(object);
+ if (CACHE_REGISTER_SIZE) this.cachePerformance.registerEvent(eventType, identifier, CachePerformance.calculateObjectSize(object));
+ else this.cachePerformance.registerEvent(eventType, identifier);
+ if (eventType.equals(EventType.ADDITION)) this.cachePerformance.registerSize(size());
+ }
+
+ public List<V> values() {
+ return this.cache.asMap().values().stream()
+ .map(value -> value.orElse(null))
+ .collect(Collectors.toList());
+ }
}
src/main/java/br/ufrgs/inf/prosoft/cache/tools/CSV.java 415(+188 -227)
diff --git a/src/main/java/br/ufrgs/inf/prosoft/cache/tools/CSV.java b/src/main/java/br/ufrgs/inf/prosoft/cache/tools/CSV.java
index 1aee4fd..6910a16 100644
--- a/src/main/java/br/ufrgs/inf/prosoft/cache/tools/CSV.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/cache/tools/CSV.java
@@ -1,281 +1,242 @@
package br.ufrgs.inf.prosoft.cache.tools;
import br.ufrgs.inf.prosoft.cache.tools.CSV.Row;
+
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
+import java.util.*;
+import java.util.stream.Stream;
public class CSV implements Iterable<Row> {
- private class Reference {
-
- String value;
-
- public Reference() {
- }
-
- public Reference(String value) {
- this.value = value;
- }
-
- @Override
- public int hashCode() {
- int hash = 7;
- hash = 11 * hash + Objects.hashCode(this.value);
- return hash;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this.value == null) {
- return obj == null;
- }
- if (obj instanceof Reference) {
- return this.value.equals(((Reference) obj).value);
- }
- if (obj instanceof String) {
- return this.value.equals((String) obj);
- }
- return false;
- }
-
- @Override
- public String toString() {
- return value;
- }
+ private final List<Header> headers;
+ private final List<Row> rows;
+ private final List<Column> columns;
+ private final Map<Header, Map<Reference, Value>> indexes;
+
+ public CSV(String... headers) {
+ this(headers, new String[]{});
+ }
+
+ public CSV(String[] headers, String[] indexes) {
+ this.headers = new ArrayList<>();
+ this.indexes = new HashMap<>();
+ this.columns = new ArrayList<>();
+ this.rows = new ArrayList<>();
+
+ ArrayList<String> indexesList = new ArrayList<>(Arrays.asList(indexes));
+
+ for (String strHeader : headers) {
+ Header header = new Header(strHeader);
+ this.headers.add(header);
+ if (indexesList.contains(header.toString())) {
+ indexesList.remove(header.toString());
+ this.indexes.put(header, new HashMap<>());
+ }
+ this.columns.add(new Column());
}
-
- private class Header extends Reference {
-
- public Header(String value) {
- super(value);
- }
-
+ if (!indexesList.isEmpty()) throw new RuntimeException("indexes not present on headers: " + indexesList);
+ }
+
+ private static String[] process(String line) {
+ if (line.contains("'") || line.contains("\"")) throw new UnsupportedOperationException("Scape values is not supported yet");
+ return line.split(",");
+ }
+
+ public static CSV read(String path) {
+ return read(path, new String[]{});
+ }
+
+ public static CSV read(String path, String... indexes) {
+ try {
+ List<String> lines = Files.readAllLines(Paths.get(path));
+ if (lines.isEmpty()) throw new RuntimeException("Empty file");
+ String headers = lines.remove(0);
+ CSV csv = new CSV(process(headers), indexes);
+ lines.forEach(line -> csv.append(process(line)));
+ return csv;
+ } catch (IOException ex) {
+ throw new RuntimeException("Cannot read file");
+ }
+ }
+
+ public CSV append(String... values) {
+ if (values.length != this.headers.size()) throw new RuntimeException("Values not matching headers length");
+ List<Value> list = new ArrayList<>();
+ Row row = new Row(list);
+ Iterator<Column> columnsIterator = this.columns.iterator();
+ Iterator<Header> headersIterator = this.headers.iterator();
+ for (String strValue : values) {
+ Header header = headersIterator.next();
+ Reference reference = new Reference(strValue);
+ Value value = new Value(header, reference, row, columnsIterator.next());
+ list.add(value);
+ if (this.indexes.containsKey(header)) {
+ if (this.indexes.get(header).get(reference) != null) throw new RuntimeException("duplicate index " + reference + " for " + header);
+ this.indexes.get(header).put(reference, value);
+ }
+ }
+ this.rows.add(row);
+ return this;
+ }
+
+ public Optional<Row> selectFirst(String property, String value) {
+ Header mockProperty = new Header(property);
+ Reference mockValue = new Reference(value);
+ if (!this.headers.contains(mockProperty)) throw new RuntimeException("header not present");
+ Map<Reference, Value> values = this.indexes.get(mockProperty);
+ if (values != null) return Optional.ofNullable(values.get(mockValue).getRow());
+
+ for (Row row : this) if (row.get(property).equals(value)) return Optional.ofNullable(row);
+ return Optional.empty();
+ }
+
+ public List<Row> select(String property, String value) {
+ Header mockProperty = new Header(property);
+ Reference mockValue = new Reference(value);
+ if (!this.headers.contains(mockProperty)) throw new RuntimeException("header not present");
+ List<Row> select = new ArrayList<>();
+
+ Map<Reference, Value> values = this.indexes.get(mockProperty);
+ if (values != null) {
+ select.add(values.get(mockValue).getRow());
+ return select;
}
- private class Value {
-
- private final Header header;
- private Reference reference;
- private final Row row;
- private final Column column;
-
- public Value(Header header, Row row, Column column) {
- this(header, null, row, column);
- }
+ for (Row row : this) if (row.get(property).equals(value)) select.add(row);
+ return select;
+ }
- public Value(Header header, Reference reference, Row row, Column column) {
- this.header = header;
- this.reference = reference;
- this.row = row;
- this.column = column;
- }
+ @Override
+ public Iterator<Row> iterator() {
+ return this.rows.iterator();
+ }
- public String getProperty() {
- return header.value;
- }
+ public Stream<Row> stream() {
+ return this.rows.stream();
+ }
- public String getValue() {
- return reference.value;
- }
+ private static class Reference {
- private void setValue(Reference reference) {
- this.reference = reference;
- }
+ String value;
- public Row getRow() {
- return row;
- }
+ public Reference() {
+ }
- public Column getColumn() {
- return column;
- }
+ public Reference(String value) {
+ this.value = value;
+ }
- @Override
- public String toString() {
- return getProperty() + ": " + getValue();
- }
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 11 * hash + Objects.hashCode(this.value);
+ return hash;
+ }
+ @Override
+ public boolean equals(Object obj) {
+ if (this.value == null) return obj == null;
+ if (obj instanceof Reference) return this.value.equals(((Reference) obj).value);
+ if (obj instanceof String) return this.value.equals(obj);
+ return false;
}
- protected class Row implements Iterable<Value> {
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
- private final List<Value> values;
+ private static class Header extends Reference {
- public Row(List<Value> values) {
- if (values == null) {
- throw new RuntimeException("Tried to create a null row");
- }
- this.values = values;
- }
+ public Header(String value) {
+ super(value);
+ }
- public String get(String property) {
- for (Value value : this) {
- if (value.getProperty().equals(property)) {
- return value.getValue();
- }
- }
- throw new RuntimeException("data does not contain property " + property);
- }
+ }
- @Override
- public Iterator<Value> iterator() {
- return this.values.iterator();
- }
+ private class Value {
- @Override
- public String toString() {
- return values.toString();
- }
+ private final Header header;
+ private final Row row;
+ private final Column column;
+ private Reference reference;
+ public Value(Header header, Row row, Column column) {
+ this(header, null, row, column);
}
- private class Column extends Row {
+ public Value(Header header, Reference reference, Row row, Column column) {
+ this.header = header;
+ this.reference = reference;
+ this.row = row;
+ this.column = column;
+ }
- public Column() {
- this(new ArrayList<>());
- }
+ public String getProperty() {
+ return header.value;
+ }
- public Column(List<Value> values) {
- super(values);
- }
+ public String getValue() {
+ return reference.value;
}
- private List<Header> headers;
- private final List<Row> rows;
- private final List<Column> columns;
- private final Map<Header, Map<Reference, Value>> indexes;
+ private void setValue(Reference reference) {
+ this.reference = reference;
+ }
- public CSV(String... headers) {
- this(headers, new String[]{});
+ public Row getRow() {
+ return row;
}
- public CSV(String[] headers, String[] indexes) {
- this.headers = new ArrayList<>();
- this.indexes = new HashMap<>();
- this.columns = new ArrayList<>();
- this.rows = new ArrayList<>();
-
- ArrayList<String> indexesList = new ArrayList(Arrays.asList(indexes));
-
- for (String strHeader : headers) {
- Header header = new Header(strHeader);
- this.headers.add(header);
- if (indexesList.contains(header.toString())) {
- indexesList.remove(header.toString());
- this.indexes.put(header, new HashMap<>());
- }
- this.columns.add(new Column());
- }
- if (!indexesList.isEmpty()) {
- throw new RuntimeException("indexes not present on headers: " + indexesList);
- }
+ public Column getColumn() {
+ return column;
}
- public CSV append(String... values) {
- if (values.length != this.headers.size()) {
- throw new RuntimeException("Values not matching headers length");
- }
- List<Value> list = new ArrayList<>();
- Row row = new Row(list);
- Iterator<Column> columnsIterator = this.columns.iterator();
- Iterator<Header> headersIterator = this.headers.iterator();
- for (String strValue : values) {
- Header header = headersIterator.next();
- Reference reference = new Reference(strValue);
- Value value = new Value(header, reference, row, columnsIterator.next());
- list.add(value);
- if (this.indexes.containsKey(header)) {
- if (this.indexes.get(header).get(reference) != null) {
- throw new RuntimeException("duplicate index " + reference + " for " + header);
- }
- this.indexes.get(header).put(reference, value);
- }
- }
- this.rows.add(row);
- return this;
+ @Override
+ public String toString() {
+ return getProperty() + ": " + getValue();
}
- private static String[] process(String line) {
- if (line.contains("'") || line.contains("\"")) {
- throw new UnsupportedOperationException("Scaped values not supported yet");
- }
- return line.split(",");
+ }
+
+ public class Row implements Iterable<Value> {
+
+ private final List<Value> values;
+
+ public Row(List<Value> values) {
+ if (values == null) throw new RuntimeException("Tried to create a null row");
+ this.values = values;
}
- public static CSV read(String path) {
- return read(path, new String[]{});
+ public String get(String property) {
+ for (Value value : this) if (value.getProperty().equals(property)) return value.getValue();
+ throw new RuntimeException("data does not contain property " + property);
}
- public static CSV read(String path, String... indexes) {
- try {
- List<String> lines = Files.readAllLines(Paths.get(path));
- if (lines.isEmpty()) {
- throw new RuntimeException("Empty file");
- }
- String headers = lines.remove(0);
- CSV csv = new CSV(process(headers), indexes);
- lines.forEach(line -> {
- csv.append(process(line));
- });
- return csv;
- } catch (IOException ex) {
- throw new RuntimeException("Cannot read file");
- }
+ @Override
+ public Iterator<Value> iterator() {
+ return this.values.iterator();
}
- public Optional<Row> selectFirst(String property, String value) {
- Header mockProperty = new Header(property);
- Reference mockValue = new Reference(value);
- if (!this.headers.contains(mockProperty)) {
- throw new RuntimeException("header not present");
- }
- Map<Reference, Value> values = this.indexes.get(mockProperty);
- if (values != null) {
- return Optional.ofNullable(values.get(mockValue).getRow());
- }
-
- for (Row row : this) {
- if (row.get(property).equals(value)) {
- return Optional.ofNullable(row);
- }
- }
- return Optional.empty();
+ @Override
+ public String toString() {
+ return values.toString();
}
- public List<Row> select(String property, String value) {
- Header mockProperty = new Header(property);
- Reference mockValue = new Reference(value);
- if (!this.headers.contains(mockProperty)) {
- throw new RuntimeException("header not present");
- }
- List<Row> select = new ArrayList<>();
-
- Map<Reference, Value> values = this.indexes.get(mockProperty);
- if (values != null) {
- select.add(values.get(mockValue).getRow());
- return select;
- }
-
- for (Row row : this) {
- if (row.get(property).equals(value)) {
- select.add(row);
- }
- }
- return select;
+ }
+
+ private class Column extends Row {
+
+ public Column() {
+ this(new ArrayList<>());
}
- @Override
- public Iterator<Row> iterator() {
- return this.rows.iterator();
+ public Column(List<Value> values) {
+ super(values);
}
+ }
}