View Javadoc
1   package com.guinetik.hexafun.hexa;
2   
3   import java.nio.file.DirectoryStream.Filter;
4   import java.util.ArrayList;
5   import java.util.HashMap;
6   import java.util.List;
7   import java.util.Map;
8   import java.util.function.Function;
9   import java.util.stream.Collectors;
10  
11  import com.guinetik.hexafun.fun.Result;
12  
13  /**
14   * Generic in-memory implementation of the HexaRepo interface.
15   * Uses a HashMap to store entities with string IDs.
16   * 
17   * @param <T> The entity type this repository manages
18   */
19  public class InMemoryHexaRepo<T> implements HexaRepo<T> {
20  
21      private final Map<String, T> storage = new HashMap<>();
22      private final Function<T, String> idExtractor;
23      private final Function<T, T> idGenerator;
24  
25      /**
26       * Creates a new InMemoryHexaRepo with custom ID extraction and generation.
27       * 
28       * @param idExtractor Function to extract ID from an entity
29       * @param idGenerator Function to generate a new ID for an entity that doesn't have one
30       */
31      public InMemoryHexaRepo(Function<T, String> idExtractor, Function<T, T> idGenerator) {
32          this.idExtractor = idExtractor;
33          this.idGenerator = idGenerator;
34      }
35  
36      /**
37       * Creates a new InMemoryHexaRepo with default UUID generation.
38       * Note: This constructor assumes the entity has an ID field named "id" 
39       * and is accessible via reflection or a setter.
40       * 
41       * @param idExtractor Function to extract ID from an entity
42       */
43      public InMemoryHexaRepo(Function<T, String> idExtractor) {
44          this.idExtractor = idExtractor;
45          this.idGenerator = entity -> {
46              // This is a placeholder. In a real implementation, 
47              // you would need to set the ID on the entity.
48              // This requires knowing the entity structure.
49              return entity;
50          };
51      }
52      
53      /**
54       * Creates a new InMemoryHexaRepo with default behavior.
55       * The idExtractor returns the ID from storage based on the entity.
56       * The idGenerator simply returns the entity as is.
57       */
58      public InMemoryHexaRepo() {
59          this.idExtractor = entity -> {
60              for (Map.Entry<String, T> entry : storage.entrySet()) {
61                  if (entry.getValue().equals(entity)) {
62                      return entry.getKey();
63                  }
64              }
65              return String.valueOf(storage.size() + 1);
66          };
67          this.idGenerator = entity -> entity;
68      }
69  
70      //-------------------------------------------------------------------------
71      // Create operations
72      //-------------------------------------------------------------------------
73  
74      @Override
75      public Result<T> save(T entity) {
76          try {
77              String id = idExtractor.apply(entity);
78              
79              // If no ID, generate one
80              if (id == null || id.isEmpty()) {
81                  entity = idGenerator.apply(entity);
82                  id = idExtractor.apply(entity);
83              }
84              
85              storage.put(id, entity);
86              return Result.ok(entity);
87          } catch (Exception e) {
88              return Result.fail("Failed to save entity: " + e.getMessage());
89          }
90      }
91  
92      @Override
93      public Result<List<T>> saveAll(List<T> entities) {
94          try {
95              List<T> savedEntities = new ArrayList<>();
96              for (T entity : entities) {
97                  Result<T> result = save(entity);
98                  if (result.isFailure()) {
99                      return Result.fail("Failed to save entities: " + result.error());
100                 }
101                 savedEntities.add(result.get());
102             }
103             return Result.ok(savedEntities);
104         } catch (Exception e) {
105             return Result.fail("Failed to save entities: " + e.getMessage());
106         }
107     }
108 
109     //-------------------------------------------------------------------------
110     // Read operations
111     //-------------------------------------------------------------------------
112 
113     @Override
114     public Result<T> findById(String id) {
115         if (id == null || id.isEmpty()) {
116             return Result.fail("ID cannot be null or empty");
117         }
118         
119         T entity = storage.get(id);
120         if (entity == null) {
121             return Result.fail("Entity not found with ID: " + id);
122         }
123         
124         return Result.ok(entity);
125     }
126 
127     @Override
128     public Result<List<T>> findAll() {
129         return Result.ok(new ArrayList<>(storage.values()));
130     }
131 
132     @Override
133     public Result<List<T>> findBy(Filter<T> filter) {
134         try {
135             // Since DirectoryStream.Filter only has an accept method,
136             // we need to create our own filtering logic
137             List<T> result = storage.values().stream()
138                 .filter(entity -> {
139                     try {
140                         return filter.accept(entity);
141                     } catch (Exception e) {
142                         return false;
143                     }
144                 })
145                 .collect(Collectors.toList());
146             
147             return Result.ok(result);
148         } catch (Exception e) {
149             return Result.fail("Failed to filter entities: " + e.getMessage());
150         }
151     }
152 
153     @Override
154     public Result<List<T>> findAll(int offset, int limit) {
155         if (offset < 0) {
156             return Result.fail("Offset cannot be negative");
157         }
158         if (limit < 0) {
159             return Result.fail("Limit cannot be negative");
160         }
161         
162         List<T> allEntities = new ArrayList<>(storage.values());
163         int fromIndex = Math.min(offset, allEntities.size());
164         int toIndex = Math.min(offset + limit, allEntities.size());
165         
166         return Result.ok(allEntities.subList(fromIndex, toIndex));
167     }
168 
169     @Override
170     public Result<Long> count() {
171         return Result.ok((long) storage.size());
172     }
173 
174     //-------------------------------------------------------------------------
175     // Update operations
176     //-------------------------------------------------------------------------
177 
178     @Override
179     public Result<T> update(String id, T entity) {
180         if (id == null || id.isEmpty()) {
181             return Result.fail("ID cannot be null or empty");
182         }
183         
184         if (!storage.containsKey(id)) {
185             return Result.fail("Cannot update: Entity not found with ID: " + id);
186         }
187         
188         try {
189             // Ensure the entity has the correct ID
190             String entityId = idExtractor.apply(entity);
191             if (entityId == null || !entityId.equals(id)) {
192                 return Result.fail("Entity ID doesn't match the provided ID");
193             }
194             
195             storage.put(id, entity);
196             return Result.ok(entity);
197         } catch (Exception e) {
198             return Result.fail("Failed to update entity: " + e.getMessage());
199         }
200     }
201 
202     //-------------------------------------------------------------------------
203     // Delete operations
204     //-------------------------------------------------------------------------
205 
206     @Override
207     public Result<Boolean> deleteById(String id) {
208         if (id == null || id.isEmpty()) {
209             return Result.fail("ID cannot be null or empty");
210         }
211         
212         if (!storage.containsKey(id)) {
213             return Result.ok(false); // Entity doesn't exist, so technically delete succeeded
214         }
215         
216         storage.remove(id);
217         return Result.ok(true);
218     }
219 
220     @Override
221     public Result<Void> deleteAllById(List<String> ids) {
222         if (ids == null) {
223             return Result.fail("IDs list cannot be null");
224         }
225         
226         for (String id : ids) {
227             if (id != null && !id.isEmpty()) {
228                 storage.remove(id);
229             }
230         }
231         
232         return Result.ok(null);
233     }
234 
235     @Override
236     public Result<Void> clear() {
237         storage.clear();
238         return Result.ok(null);
239     }
240 }