| 1 | package com.reallifedeveloper.tools.test.database.inmemory; | |
| 2 | ||
| 3 | import java.lang.reflect.Constructor; | |
| 4 | import java.lang.reflect.Field; | |
| 5 | import java.lang.reflect.InvocationTargetException; | |
| 6 | import java.lang.reflect.Method; | |
| 7 | import java.util.ArrayList; | |
| 8 | import java.util.Collections; | |
| 9 | import java.util.HashMap; | |
| 10 | import java.util.List; | |
| 11 | import java.util.Map; | |
| 12 | import java.util.Objects; | |
| 13 | import java.util.Optional; | |
| 14 | ||
| 15 | import org.checkerframework.checker.nullness.qual.NonNull; | |
| 16 | import org.checkerframework.checker.nullness.qual.Nullable; | |
| 17 | import org.springframework.dao.EmptyResultDataAccessException; | |
| 18 | import org.springframework.data.domain.Example; | |
| 19 | import org.springframework.data.domain.Page; | |
| 20 | import org.springframework.data.domain.PageImpl; | |
| 21 | import org.springframework.data.domain.Pageable; | |
| 22 | import org.springframework.data.domain.Sort; | |
| 23 | import org.springframework.data.repository.CrudRepository; | |
| 24 | import org.springframework.data.repository.PagingAndSortingRepository; | |
| 25 | import org.springframework.data.repository.query.QueryByExampleExecutor; | |
| 26 | ||
| 27 | import com.reallifedeveloper.tools.test.TestUtil; | |
| 28 | ||
| 29 | /** | |
| 30 | * An abstract helper class that implements the {@link CrudRepository} interface using an in-memory map instead of a database. | |
| 31 | * <p> | |
| 32 | * Contains useful methods for sub-classes implementing in-memory versions of repositories, such as {@link #findByField(String, Object)} and | |
| 33 | * {@link #findByUniqueField(String, Object)}. | |
| 34 | * | |
| 35 | * @param <T> the type of the entities handled by this repository | |
| 36 | * @param <ID> the type of the entities' primary keys | |
| 37 | * | |
| 38 | * @author RealLifeDeveloper | |
| 39 | */ | |
| 40 | @SuppressWarnings({ "PMD", "checkstyle:noReturnNull" }) // TODO: Consider refactoring this class using the hints from PMD | |
| 41 | public abstract class AbstractInMemoryCrudRepository<T, ID extends Comparable<ID>> | |
| 42 | implements CrudRepository<T, ID>, PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> { | |
| 43 | ||
| 44 | private final Map<@NonNull ID, @NonNull T> entities = new HashMap<>(); | |
| 45 | ||
| 46 | private final @Nullable PrimaryKeyGenerator<ID> primaryKeyGenerator; | |
| 47 | ||
| 48 | /** | |
| 49 | * Creates a new {@code InMemoryCrudRepository} with no primary key generator. If an entity with a {@code null} primary key is saved, an | |
| 50 | * exception is thrown. | |
| 51 | */ | |
| 52 | public AbstractInMemoryCrudRepository() { | |
| 53 | this.primaryKeyGenerator = null; | |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * Creates a new {@code InMemoryCrudRepository} with the provided primary key generator. If an entity with a {@code null} primary key is | |
| 58 | * saved, the generator is used to create a new primary key that is stored in the entity before saving. | |
| 59 | * | |
| 60 | * @param primaryKeyGenerator the primary key generator to use, must not be {@code null} | |
| 61 | */ | |
| 62 | public AbstractInMemoryCrudRepository(PrimaryKeyGenerator<ID> primaryKeyGenerator) { | |
| 63 |
1
1. <init> : negated conditional → KILLED |
if (primaryKeyGenerator == null) { |
| 64 | throw new IllegalArgumentException("primaryKeyGenerator must not be null"); | |
| 65 | } | |
| 66 | this.primaryKeyGenerator = primaryKeyGenerator; | |
| 67 | } | |
| 68 | ||
| 69 | /** | |
| 70 | * Finds entities with a field matching a value. | |
| 71 | * | |
| 72 | * @param fieldName the name of the field to use when searching | |
| 73 | * @param value the value to search for | |
| 74 | * @param <F> the type of {@code value} | |
| 75 | * @return a list of entities {@code e} such that {@code value.equals(e.fieldName)} | |
| 76 | * | |
| 77 | * @throws IllegalArgumentException if {@code fieldName} is {@code null} | |
| 78 | */ | |
| 79 | protected <F> List<@NonNull T> findByField(String fieldName, F value) { | |
| 80 |
1
1. findByField : negated conditional → KILLED |
if (fieldName == null) { |
| 81 | throw new IllegalArgumentException("fieldName must not be null"); | |
| 82 | } | |
| 83 |
3
1. findByField : replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::findByField → KILLED 2. lambda$findByField$0 : replaced boolean return with true for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::lambda$findByField$0 → KILLED 3. lambda$findByField$0 : replaced boolean return with false for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::lambda$findByField$0 → KILLED |
return entities.values().stream().filter(entity -> Objects.equals(value, TestUtil.getFieldValue(entity, fieldName))).toList(); |
| 84 | } | |
| 85 | ||
| 86 | /** | |
| 87 | * Finds a unique entity with a field matching a value. | |
| 88 | * | |
| 89 | * @param fieldName the name of the field to use when searching | |
| 90 | * @param value the value to search for | |
| 91 | * @param <F> the type of {@code value} | |
| 92 | * | |
| 93 | * @return the unique entity {@code e} such that {@code value.equals(e.fieldName)}, or {@code null} if no such entity is found | |
| 94 | * | |
| 95 | * @throws IllegalArgumentException if either argument is {@code null}, or if more than one entity with the given value is found | |
| 96 | */ | |
| 97 | protected <F> Optional<T> findByUniqueField(String fieldName, F value) { | |
| 98 | List<T> foundEntities = findByField(fieldName, value); | |
| 99 |
1
1. findByUniqueField : negated conditional → KILLED |
if (foundEntities.isEmpty()) { |
| 100 | return Optional.empty(); | |
| 101 |
1
1. findByUniqueField : negated conditional → KILLED |
} else if (foundEntities.size() == 1) { |
| 102 |
1
1. findByUniqueField : replaced return value with Optional.empty for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::findByUniqueField → KILLED |
return Optional.of(foundEntities.get(0)); |
| 103 | } else { | |
| 104 | throw new IllegalArgumentException( | |
| 105 | "Field " + fieldName + " is not unique, found " + foundEntities.size() + " entities: " + foundEntities); | |
| 106 | } | |
| 107 | } | |
| 108 | ||
| 109 | /** | |
| 110 | * {@inheritDoc} | |
| 111 | */ | |
| 112 | @Override | |
| 113 | public long count() { | |
| 114 |
1
1. count : replaced long return with 0 for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::count → KILLED |
return entities.size(); |
| 115 | } | |
| 116 | ||
| 117 | /** | |
| 118 | * {@inheritDoc} | |
| 119 | */ | |
| 120 | @Override | |
| 121 | public void deleteById(ID id) { | |
| 122 |
1
1. deleteById : negated conditional → KILLED |
if (id == null) { |
| 123 | throw new IllegalArgumentException("id must not be null"); | |
| 124 | } | |
| 125 | T removedEntity = entities.remove(id); | |
| 126 |
1
1. deleteById : negated conditional → KILLED |
if (removedEntity == null) { |
| 127 | throw new EmptyResultDataAccessException("Entity with id " + id + " not found", 1); | |
| 128 | } | |
| 129 | } | |
| 130 | ||
| 131 | /** | |
| 132 | * {@inheritDoc} | |
| 133 | */ | |
| 134 | @Override | |
| 135 | public void deleteAll(Iterable<? extends T> entitiesToDelete) { | |
| 136 |
1
1. deleteAll : negated conditional → KILLED |
if (entitiesToDelete == null) { |
| 137 | throw new IllegalArgumentException("entitiesToDelete must not be null"); | |
| 138 | } | |
| 139 | for (T entity : entitiesToDelete) { | |
| 140 |
1
1. deleteAll : removed call to com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::delete → KILLED |
delete(entity); |
| 141 | } | |
| 142 | } | |
| 143 | ||
| 144 | /** | |
| 145 | * {@inheritDoc} | |
| 146 | */ | |
| 147 | @Override | |
| 148 | public void deleteAllById(Iterable<? extends ID> ids) { | |
| 149 | for (ID id : ids) { | |
| 150 |
1
1. deleteAllById : removed call to com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::deleteById → KILLED |
deleteById(id); |
| 151 | } | |
| 152 | } | |
| 153 | ||
| 154 | /** | |
| 155 | * {@inheritDoc} | |
| 156 | */ | |
| 157 | @Override | |
| 158 | public void delete(T entity) { | |
| 159 |
1
1. delete : negated conditional → KILLED |
if (entity == null) { |
| 160 | throw new IllegalArgumentException("entity must not be null"); | |
| 161 | } | |
| 162 | entities.remove(getId(entity)); | |
| 163 | } | |
| 164 | ||
| 165 | /** | |
| 166 | * {@inheritDoc} | |
| 167 | */ | |
| 168 | @Override | |
| 169 | public void deleteAll() { | |
| 170 |
1
1. deleteAll : removed call to java/util/Map::clear → KILLED |
entities.clear(); |
| 171 | } | |
| 172 | ||
| 173 | /** | |
| 174 | * {@inheritDoc} | |
| 175 | */ | |
| 176 | @Override | |
| 177 | public boolean existsById(ID id) { | |
| 178 |
1
1. existsById : negated conditional → KILLED |
if (id == null) { |
| 179 | throw new IllegalArgumentException("id must not be null"); | |
| 180 | } | |
| 181 |
2
1. existsById : replaced boolean return with true for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::existsById → KILLED 2. existsById : replaced boolean return with false for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::existsById → KILLED |
return entities.containsKey(id); |
| 182 | } | |
| 183 | ||
| 184 | /** | |
| 185 | * {@inheritDoc} | |
| 186 | */ | |
| 187 | @Override | |
| 188 | public List<T> findAll() { | |
| 189 |
1
1. findAll : replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::findAll → KILLED |
return new ArrayList<>(entities.values()); |
| 190 | } | |
| 191 | ||
| 192 | /** | |
| 193 | * {@inheritDoc} | |
| 194 | */ | |
| 195 | @Override | |
| 196 | public List<T> findAllById(Iterable<ID> ids) { | |
| 197 |
1
1. findAllById : negated conditional → KILLED |
if (ids == null) { |
| 198 | throw new IllegalArgumentException("ids must not be null"); | |
| 199 | } | |
| 200 | List<T> selectedEntities = new ArrayList<T>(); | |
| 201 | for (ID id : ids) { | |
| 202 | Optional<T> optionalEntity = findById(id); | |
| 203 |
1
1. findAllById : negated conditional → KILLED |
if (optionalEntity.isPresent()) { |
| 204 | selectedEntities.add(optionalEntity.get()); | |
| 205 | } | |
| 206 | } | |
| 207 |
1
1. findAllById : replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::findAllById → KILLED |
return selectedEntities; |
| 208 | } | |
| 209 | ||
| 210 | /** | |
| 211 | * {@inheritDoc} | |
| 212 | */ | |
| 213 | @Override | |
| 214 | public Optional<T> findById(ID id) { | |
| 215 |
1
1. findById : negated conditional → KILLED |
if (id == null) { |
| 216 | throw new IllegalArgumentException("id must not be null"); | |
| 217 | } | |
| 218 | T item = entities.get(id); | |
| 219 |
1
1. findById : replaced return value with Optional.empty for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::findById → KILLED |
return Optional.ofNullable(item); |
| 220 | } | |
| 221 | ||
| 222 | /** | |
| 223 | * {@inheritDoc} | |
| 224 | */ | |
| 225 | @Override | |
| 226 | public <S extends T> S save(S entity) { | |
| 227 |
1
1. save : negated conditional → KILLED |
if (entity == null) { |
| 228 | throw new IllegalArgumentException("entity must not be null"); | |
| 229 | } | |
| 230 | ID id = getId(entity); | |
| 231 |
1
1. save : negated conditional → KILLED |
if (id == null) { |
| 232 |
1
1. save : negated conditional → KILLED |
if (primaryKeyGenerator != null) { |
| 233 | id = primaryKeyGenerator.nextPrimaryKey(maximumPrimaryKey()); | |
| 234 |
1
1. save : removed call to com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::setId → KILLED |
setId(entity, id); |
| 235 | } else { | |
| 236 | throw new IllegalStateException("Primary key is null and no primary key generator available: entity=" + entity); | |
| 237 | } | |
| 238 | } | |
| 239 | entities.put(id, entity); | |
| 240 |
1
1. save : replaced return value with null for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::save → KILLED |
return entity; |
| 241 | } | |
| 242 | ||
| 243 | /** | |
| 244 | * {@inheritDoc} | |
| 245 | */ | |
| 246 | @Override | |
| 247 | public <S extends T> List<S> saveAll(Iterable<S> entitiesToSave) { | |
| 248 |
1
1. saveAll : negated conditional → KILLED |
if (entitiesToSave == null) { |
| 249 | throw new IllegalArgumentException("entitiesToSave must not be null"); | |
| 250 | } | |
| 251 | List<S> savedEntities = new ArrayList<>(); | |
| 252 | for (S entity : entitiesToSave) { | |
| 253 | savedEntities.add(save(entity)); | |
| 254 | } | |
| 255 |
1
1. saveAll : replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::saveAll → SURVIVED |
return savedEntities; |
| 256 | } | |
| 257 | ||
| 258 | // | |
| 259 | // PagingAndSortingRepository methods | |
| 260 | // | |
| 261 | ||
| 262 | /** | |
| 263 | * {@inheritDoc} | |
| 264 | */ | |
| 265 | @Override | |
| 266 | public List<T> findAll(Sort sort) { | |
| 267 |
1
1. findAll : replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::findAll → KILLED |
return SortUtil.sort(findAll(), sort); |
| 268 | } | |
| 269 | ||
| 270 | /** | |
| 271 | * {@inheritDoc} | |
| 272 | */ | |
| 273 | @Override | |
| 274 | public Page<T> findAll(Pageable pageable) { | |
| 275 | List<T> allEntities = SortUtil.sort(findAll(), pageable.getSort()); | |
| 276 | int start = (int) pageable.getOffset(); | |
| 277 |
4
1. findAll : changed conditional boundary → SURVIVED 2. findAll : negated conditional → KILLED 3. findAll : Replaced integer addition with subtraction → KILLED 4. findAll : Replaced integer addition with subtraction → KILLED |
int end = (start + pageable.getPageSize()) > allEntities.size() ? allEntities.size() : (start + pageable.getPageSize()); |
| 278 |
2
1. findAll : changed conditional boundary → SURVIVED 2. findAll : negated conditional → KILLED |
List<T> pagedEntities = start <= end ? allEntities.subList(start, end) : Collections.emptyList(); |
| 279 | Page<T> page = new PageImpl<>(pagedEntities, pageable, allEntities.size()); | |
| 280 |
1
1. findAll : replaced return value with null for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::findAll → KILLED |
return page; |
| 281 | } | |
| 282 | ||
| 283 | // | |
| 284 | // Helper methods | |
| 285 | // | |
| 286 | ||
| 287 | /** | |
| 288 | * Gives the value of the largest primary key in an entity currently in the repository. | |
| 289 | * <p> | |
| 290 | * This method returns {@code null} if there are no entities in the repository. Since the save methods guarantee that | |
| 291 | * | |
| 292 | * @return the largest primary key in the repository, or {@code null} if the repository is empty | |
| 293 | */ | |
| 294 | private @Nullable ID maximumPrimaryKey() { | |
| 295 | ID max = null; | |
| 296 | for (T entity : findAll()) { | |
| 297 | ID id = getId(entity); | |
| 298 |
4
1. maximumPrimaryKey : changed conditional boundary → SURVIVED 2. maximumPrimaryKey : negated conditional → KILLED 3. maximumPrimaryKey : negated conditional → KILLED 4. maximumPrimaryKey : negated conditional → KILLED |
if (max == null || (id != null && id.compareTo(max) > 0)) { |
| 299 | max = id; | |
| 300 | } | |
| 301 | } | |
| 302 |
1
1. maximumPrimaryKey : replaced return value with null for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::maximumPrimaryKey → KILLED |
return max; |
| 303 | } | |
| 304 | ||
| 305 | /** | |
| 306 | * Gives the value of the ID field or method of the given entity. | |
| 307 | * | |
| 308 | * @param entity the entity to examine, should not be {@code null} | |
| 309 | * | |
| 310 | * @return the value of the ID field or method of {@code entity}, may be {@code null} | |
| 311 | */ | |
| 312 | @SuppressWarnings("unchecked") | |
| 313 | protected @Nullable ID getId(T entity) { | |
| 314 | ID id = null; | |
| 315 | try { | |
| 316 |
1
1. getId : negated conditional → KILLED |
if (getCompositeIdClass(entity).isPresent()) { |
| 317 | // TODO: Handle IdClass with method annotations | |
| 318 | List<Field> idFields = getIdFields(entity); | |
| 319 | id = createIdClassInstance(entity, idFields); | |
| 320 |
1
1. getId : negated conditional → KILLED |
} else if (getIdField(entity).isPresent()) { |
| 321 | id = (ID) getIdField(entity).get().get(entity); | |
| 322 |
1
1. getId : negated conditional → KILLED |
} else if (getIdMethod(entity).isPresent()) { |
| 323 | id = (ID) getIdMethod(entity).get().invoke(entity); | |
| 324 | } else { | |
| 325 | throw new IllegalArgumentException("Entity has no @Id annotation: " + entity); | |
| 326 | } | |
| 327 | } catch (ReflectiveOperationException e) { | |
| 328 | throw new IllegalStateException(e); | |
| 329 | } | |
| 330 |
1
1. getId : replaced return value with null for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::getId → KILLED |
return id; |
| 331 | } | |
| 332 | ||
| 333 | /** | |
| 334 | * Sets the value of the ID field, or calls the ID setter method, for the given entity. | |
| 335 | * | |
| 336 | * @param entity the entity for which to set the ID | |
| 337 | * @param id the new ID value | |
| 338 | */ | |
| 339 | protected void setId(T entity, @Nullable ID id) { | |
| 340 | try { | |
| 341 |
1
1. setId : negated conditional → KILLED |
if (getCompositeIdClass(entity).isPresent()) { |
| 342 | // TODO: Handle IdClass with method annotations | |
| 343 | List<Field> idFields = getIdFields(entity); | |
| 344 |
1
1. setId : negated conditional → KILLED |
if (id == null) { |
| 345 | for (Field idField : idFields) { | |
| 346 |
1
1. setId : removed call to java/lang/reflect/Field::set → NO_COVERAGE |
idField.set(entity, null); |
| 347 | } | |
| 348 | } else { | |
| 349 |
1
1. setId : removed call to com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::setIdFieldsFromIdClassInstance → KILLED |
setIdFieldsFromIdClassInstance(entity, idFields, id); |
| 350 | } | |
| 351 |
1
1. setId : negated conditional → KILLED |
} else if (getIdField(entity).isPresent()) { |
| 352 |
1
1. setId : removed call to java/lang/reflect/Field::set → KILLED |
getIdField(entity).get().set(entity, id); |
| 353 |
1
1. setId : negated conditional → KILLED |
} else if (getIdMethod(entity).isPresent()) { |
| 354 | @SuppressWarnings("unchecked") | |
| 355 | Class<ID> idClass = (Class<ID>) getIdMethod(entity).get().getReturnType(); | |
| 356 | Method setIdMethod = getSetMethod(entity, idClass); | |
| 357 | setIdMethod.invoke(entity, id); | |
| 358 | } | |
| 359 | } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { | |
| 360 | throw new IllegalStateException(e); | |
| 361 | } | |
| 362 | } | |
| 363 | ||
| 364 | private Optional<Field> getIdField(T entity) { | |
| 365 | List<Field> idFields = getIdFields(entity); | |
| 366 |
1
1. getIdField : negated conditional → KILLED |
if (idFields.isEmpty()) { |
| 367 | return Optional.empty(); | |
| 368 |
2
1. getIdField : changed conditional boundary → KILLED 2. getIdField : negated conditional → KILLED |
} else if (idFields.size() > 1) { |
| 369 | throw new IllegalStateException("Multiptle ID fields found in entity: " + entity); | |
| 370 | } else { | |
| 371 |
1
1. getIdField : replaced return value with Optional.empty for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::getIdField → KILLED |
return Optional.of(idFields.get(0)); |
| 372 | } | |
| 373 | } | |
| 374 | ||
| 375 | private List<Field> getIdFields(T entity) { | |
| 376 | List<Field> idFields = new ArrayList<>(); | |
| 377 | Class<?> c = entity.getClass(); | |
| 378 |
1
1. getIdFields : negated conditional → KILLED |
while (c != null) { |
| 379 | for (Field field : c.getDeclaredFields()) { | |
| 380 |
1
1. getIdFields : negated conditional → KILLED |
if (isIdField(field)) { |
| 381 |
1
1. getIdFields : removed call to java/lang/reflect/Field::setAccessible → KILLED |
field.setAccessible(true); |
| 382 | idFields.add(field); | |
| 383 | } | |
| 384 | } | |
| 385 | c = c.getSuperclass(); | |
| 386 | } | |
| 387 |
1
1. getIdFields : replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::getIdFields → KILLED |
return idFields; |
| 388 | } | |
| 389 | ||
| 390 | private Optional<Method> getIdMethod(T entity) { | |
| 391 | List<Method> idMethods = getIdMethods(entity); | |
| 392 |
1
1. getIdMethod : negated conditional → KILLED |
if (idMethods.isEmpty()) { |
| 393 | return Optional.empty(); | |
| 394 |
2
1. getIdMethod : negated conditional → KILLED 2. getIdMethod : changed conditional boundary → KILLED |
} else if (idMethods.size() > 1) { |
| 395 | throw new IllegalStateException("Multiptle ID methods found in entity: " + entity); | |
| 396 | } else { | |
| 397 |
1
1. getIdMethod : replaced return value with Optional.empty for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::getIdMethod → KILLED |
return Optional.of(idMethods.get(0)); |
| 398 | } | |
| 399 | } | |
| 400 | ||
| 401 | private List<Method> getIdMethods(T entity) { | |
| 402 | List<Method> idMethods = new ArrayList<>(); | |
| 403 | Class<?> c = entity.getClass(); | |
| 404 |
1
1. getIdMethods : negated conditional → KILLED |
while (c != null) { |
| 405 | for (Method method : c.getDeclaredMethods()) { | |
| 406 |
1
1. getIdMethods : negated conditional → KILLED |
if (isIdMethod(method)) { |
| 407 |
1
1. getIdMethods : removed call to java/lang/reflect/Method::setAccessible → SURVIVED |
method.setAccessible(true); |
| 408 | idMethods.add(method); | |
| 409 | } | |
| 410 | } | |
| 411 | c = c.getSuperclass(); | |
| 412 | } | |
| 413 |
1
1. getIdMethods : replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::getIdMethods → KILLED |
return idMethods; |
| 414 | } | |
| 415 | ||
| 416 | private Method getSetMethod(T entity, Class<ID> idClass) throws NoSuchMethodException { | |
| 417 | Method getMethod = getIdMethod(entity) | |
| 418 |
1
1. lambda$getSetMethod$1 : replaced return value with null for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::lambda$getSetMethod$1 → NO_COVERAGE |
.orElseThrow(() -> new NoSuchMethodException("Get method for ID not found: entity=" + entity)); |
| 419 | String setMethodName = getMethod.getName().replaceFirst("^get", "set"); | |
| 420 | Method setMethod = entity.getClass().getMethod(setMethodName, idClass); | |
| 421 |
1
1. getSetMethod : removed call to java/lang/reflect/Method::setAccessible → SURVIVED |
setMethod.setAccessible(true); |
| 422 |
1
1. getSetMethod : replaced return value with null for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::getSetMethod → KILLED |
return setMethod; |
| 423 | } | |
| 424 | ||
| 425 | private @Nullable ID createIdClassInstance(T entity, List<Field> idFields) throws ReflectiveOperationException { | |
| 426 | Class<ID> idClass = getCompositeIdClass(entity) | |
| 427 |
1
1. lambda$createIdClassInstance$2 : replaced return value with null for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::lambda$createIdClassInstance$2 → NO_COVERAGE |
.orElseThrow(() -> new IllegalStateException("Composibte ID class not found: entity=" + entity)); |
| 428 | Constructor<ID> constructor = idClass.getDeclaredConstructor(); | |
| 429 |
1
1. createIdClassInstance : removed call to java/lang/reflect/Constructor::setAccessible → SURVIVED |
constructor.setAccessible(true); |
| 430 | ID id = constructor.newInstance(); | |
| 431 | for (Field idField : idFields) { | |
| 432 |
1
1. createIdClassInstance : negated conditional → KILLED |
if (idField.get(entity) == null) { |
| 433 | // If any of the ID fields is null, we say that the primary key is null. | |
| 434 | return null; | |
| 435 | } | |
| 436 |
1
1. createIdClassInstance : removed call to com/reallifedeveloper/tools/test/TestUtil::injectField → KILLED |
TestUtil.injectField(id, idField.getName(), idField.get(entity)); |
| 437 | } | |
| 438 |
1
1. createIdClassInstance : replaced return value with null for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::createIdClassInstance → KILLED |
return id; |
| 439 | } | |
| 440 | ||
| 441 | private void setIdFieldsFromIdClassInstance(T entity, List<Field> idFields, ID id) { | |
| 442 | for (Field idField : idFields) { | |
| 443 | Object value = TestUtil.getFieldValue(id, idField.getName()); | |
| 444 |
1
1. setIdFieldsFromIdClassInstance : removed call to com/reallifedeveloper/tools/test/TestUtil::injectField → KILLED |
TestUtil.injectField(entity, idField.getName(), value); |
| 445 | } | |
| 446 | } | |
| 447 | ||
| 448 | /** | |
| 449 | * Override this in a concrete subclass to decide if a given field is an ID field of an entity. | |
| 450 | * | |
| 451 | * @param field the field to examine | |
| 452 | * | |
| 453 | * @return {@code true} if {@code field} is an ID field, {@code false} otherwise | |
| 454 | */ | |
| 455 | protected abstract boolean isIdField(Field field); | |
| 456 | ||
| 457 | /** | |
| 458 | * Override this in a concrete subclass to decide if a given method is a method giving the ID of an entity. | |
| 459 | * | |
| 460 | * @param method the method to examine | |
| 461 | * | |
| 462 | * @return {@code true} if {@code method} is an ID method, {@code false} otherwise | |
| 463 | */ | |
| 464 | protected abstract boolean isIdMethod(Method method); | |
| 465 | ||
| 466 | /** | |
| 467 | * Override this in concrete subclass to give the ID class representing a composite primary key for an entity, if any. | |
| 468 | * | |
| 469 | * @param entity the entity to examine | |
| 470 | * | |
| 471 | * @return the ID class representing the composite primary key of {@code entity}, or an empty optional if there is no such class | |
| 472 | */ | |
| 473 | protected abstract Optional<Class<ID>> getCompositeIdClass(Object entity); | |
| 474 | ||
| 475 | @Override | |
| 476 | public String toString() { | |
| 477 |
1
1. toString : replaced return value with "" for com/reallifedeveloper/tools/test/database/inmemory/AbstractInMemoryCrudRepository::toString → KILLED |
return getClass().getSimpleName() + "{entities=" + entities + "}"; |
| 478 | } | |
| 479 | ||
| 480 | // | |
| 481 | // QueryByExampleExecutor methods | |
| 482 | // | |
| 483 | ||
| 484 | /** | |
| 485 | * {@inheritDoc} | |
| 486 | * <p> | |
| 487 | * This method is not yet implemented, so it always throws an exception. | |
| 488 | * | |
| 489 | * @throws UnsupportedOperationException always | |
| 490 | */ | |
| 491 | @Override | |
| 492 | public <S extends T> Optional<S> findOne(Example<S> example) { | |
| 493 | throw new UnsupportedOperationException("Not yet implemented"); | |
| 494 | } | |
| 495 | ||
| 496 | /** | |
| 497 | * {@inheritDoc} | |
| 498 | * <p> | |
| 499 | * This method is not yet implemented, so it always throws an exception. | |
| 500 | * | |
| 501 | * @throws UnsupportedOperationException always | |
| 502 | */ | |
| 503 | @Override | |
| 504 | public <S extends T> List<S> findAll(Example<S> example) { | |
| 505 | throw new UnsupportedOperationException("Not yet implemented"); | |
| 506 | } | |
| 507 | ||
| 508 | /** | |
| 509 | * {@inheritDoc} | |
| 510 | * <p> | |
| 511 | * This method is not yet implemented, so it always throws an exception. | |
| 512 | * | |
| 513 | * @throws UnsupportedOperationException always | |
| 514 | */ | |
| 515 | @Override | |
| 516 | public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) { | |
| 517 | throw new UnsupportedOperationException("Not yet implemented"); | |
| 518 | } | |
| 519 | ||
| 520 | /** | |
| 521 | * {@inheritDoc} | |
| 522 | * <p> | |
| 523 | * This method is not yet implemented, so it always throws an exception. | |
| 524 | * | |
| 525 | * @throws UnsupportedOperationException always | |
| 526 | */ | |
| 527 | @Override | |
| 528 | public <S extends T> List<S> findAll(Example<S> example, Sort sort) { | |
| 529 | throw new UnsupportedOperationException("Not yet implemented"); | |
| 530 | } | |
| 531 | ||
| 532 | /** | |
| 533 | * {@inheritDoc} | |
| 534 | * <p> | |
| 535 | * This method is not yet implemented, so it always throws an exception. | |
| 536 | * | |
| 537 | * @throws UnsupportedOperationException always | |
| 538 | */ | |
| 539 | @Override | |
| 540 | public <S extends T> long count(Example<S> example) { | |
| 541 | throw new UnsupportedOperationException("Not yet implemented"); | |
| 542 | } | |
| 543 | ||
| 544 | /** | |
| 545 | * {@inheritDoc} | |
| 546 | * <p> | |
| 547 | * This method is not yet implemented, so it always throws an exception. | |
| 548 | * | |
| 549 | * @throws UnsupportedOperationException always | |
| 550 | */ | |
| 551 | @Override | |
| 552 | public <S extends T> boolean exists(Example<S> example) { | |
| 553 | throw new UnsupportedOperationException("Not yet implemented"); | |
| 554 | } | |
| 555 | ||
| 556 | /** | |
| 557 | * Make finalize method final to avoid "Finalizer attacks" and corresponding SpotBugs warning (CT_CONSTRUCTOR_THROW). | |
| 558 | * | |
| 559 | * @see <a href="https://wiki.sei.cmu.edu/confluence/display/java/OBJ11-J.+Be+wary+of+letting+constructors+throw+exceptions"> | |
| 560 | * Explanation of finalizer attack</a> | |
| 561 | */ | |
| 562 | @Override | |
| 563 | @SuppressWarnings({ "deprecation", "removal", "Finalize", "checkstyle:NoFinalizer", "PMD.EmptyFinalizer", | |
| 564 | "PMD.EmptyMethodInAbstractClassShouldBeAbstract" }) | |
| 565 | protected final void finalize() throws Throwable { | |
| 566 | // Do nothing | |
| 567 | } | |
| 568 | ||
| 569 | } | |
Mutations | ||
| 63 |
1.1 |
|
| 80 |
1.1 |
|
| 83 |
1.1 2.2 3.3 |
|
| 99 |
1.1 |
|
| 101 |
1.1 |
|
| 102 |
1.1 |
|
| 114 |
1.1 |
|
| 122 |
1.1 |
|
| 126 |
1.1 |
|
| 136 |
1.1 |
|
| 140 |
1.1 |
|
| 150 |
1.1 |
|
| 159 |
1.1 |
|
| 170 |
1.1 |
|
| 178 |
1.1 |
|
| 181 |
1.1 2.2 |
|
| 189 |
1.1 |
|
| 197 |
1.1 |
|
| 203 |
1.1 |
|
| 207 |
1.1 |
|
| 215 |
1.1 |
|
| 219 |
1.1 |
|
| 227 |
1.1 |
|
| 231 |
1.1 |
|
| 232 |
1.1 |
|
| 234 |
1.1 |
|
| 240 |
1.1 |
|
| 248 |
1.1 |
|
| 255 |
1.1 |
|
| 267 |
1.1 |
|
| 277 |
1.1 2.2 3.3 4.4 |
|
| 278 |
1.1 2.2 |
|
| 280 |
1.1 |
|
| 298 |
1.1 2.2 3.3 4.4 |
|
| 302 |
1.1 |
|
| 316 |
1.1 |
|
| 320 |
1.1 |
|
| 322 |
1.1 |
|
| 330 |
1.1 |
|
| 341 |
1.1 |
|
| 344 |
1.1 |
|
| 346 |
1.1 |
|
| 349 |
1.1 |
|
| 351 |
1.1 |
|
| 352 |
1.1 |
|
| 353 |
1.1 |
|
| 366 |
1.1 |
|
| 368 |
1.1 2.2 |
|
| 371 |
1.1 |
|
| 378 |
1.1 |
|
| 380 |
1.1 |
|
| 381 |
1.1 |
|
| 387 |
1.1 |
|
| 392 |
1.1 |
|
| 394 |
1.1 2.2 |
|
| 397 |
1.1 |
|
| 404 |
1.1 |
|
| 406 |
1.1 |
|
| 407 |
1.1 |
|
| 413 |
1.1 |
|
| 418 |
1.1 |
|
| 421 |
1.1 |
|
| 422 |
1.1 |
|
| 427 |
1.1 |
|
| 429 |
1.1 |
|
| 432 |
1.1 |
|
| 436 |
1.1 |
|
| 438 |
1.1 |
|
| 444 |
1.1 |
|
| 477 |
1.1 |