| 1 | /* | |
| 2 | * Copyright 2004 the original author or authors. | |
| 3 | * | |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | * you may not use this file except in compliance with the License. | |
| 6 | * You may obtain a copy of the License at | |
| 7 | * | |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | * | |
| 10 | * Unless required by applicable law or agreed to in writing, software | |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | * See the License for the specific language governing permissions and | |
| 14 | * limitations under the License. | |
| 15 | */ | |
| 16 | package com.ancientprogramming.fixedformat4j.format.impl; | |
| 17 | ||
| 18 | import com.ancientprogramming.fixedformat4j.annotation.Field; | |
| 19 | import com.ancientprogramming.fixedformat4j.annotation.Fields; | |
| 20 | import com.ancientprogramming.fixedformat4j.annotation.FixedFormatPattern; | |
| 21 | import com.ancientprogramming.fixedformat4j.annotation.Record; | |
| 22 | import com.ancientprogramming.fixedformat4j.exception.FixedFormatException; | |
| 23 | import com.ancientprogramming.fixedformat4j.format.FixedFormatManager; | |
| 24 | import com.ancientprogramming.fixedformat4j.format.FixedFormatter; | |
| 25 | import com.ancientprogramming.fixedformat4j.format.FormatContext; | |
| 26 | import com.ancientprogramming.fixedformat4j.format.FormatInstructions; | |
| 27 | import com.ancientprogramming.fixedformat4j.format.ParseException; | |
| 28 | import com.ancientprogramming.fixedformat4j.format.data.FixedFormatPatternData; | |
| 29 | import org.apache.commons.lang3.StringUtils; | |
| 30 | import org.slf4j.Logger; | |
| 31 | import org.slf4j.LoggerFactory; | |
| 32 | ||
| 33 | import java.lang.annotation.Annotation; | |
| 34 | import java.lang.reflect.AnnotatedElement; | |
| 35 | import java.lang.reflect.Method; | |
| 36 | import java.util.Collections; | |
| 37 | import java.util.HashMap; | |
| 38 | import java.util.Set; | |
| 39 | import java.util.concurrent.ConcurrentHashMap; | |
| 40 | ||
| 41 | import static com.ancientprogramming.fixedformat4j.format.FixedFormatUtil.fetchData; | |
| 42 | import static com.ancientprogramming.fixedformat4j.format.FixedFormatUtil.getFixedFormatterInstance; | |
| 43 | import static java.lang.String.format; | |
| 44 | ||
| 45 | /** | |
| 46 | * Load and export objects to and from fixed formatted string representation | |
| 47 | * | |
| 48 | * @author Jacob von Eyben - <a href="https://eybenconsult.com">https://eybenconsult.com</a> | |
| 49 | * @since 1.0.0 | |
| 50 | */ | |
| 51 | public class FixedFormatManagerImpl implements FixedFormatManager { | |
| 52 | ||
| 53 | private static final Logger LOG = LoggerFactory.getLogger(FixedFormatManagerImpl.class); | |
| 54 | private static final Set<Class<?>> VALIDATED_CLASSES = Collections.newSetFromMap(new ConcurrentHashMap<>()); | |
| 55 | ||
| 56 | private final AnnotationScanner annotationScanner = new AnnotationScanner(); | |
| 57 | private final FormatInstructionsBuilder instructionsBuilder = new FormatInstructionsBuilder(); | |
| 58 | private final RecordInstantiator recordInstantiator = new RecordInstantiator(); | |
| 59 | private final RepeatingFieldSupport repeatingFieldSupport = new RepeatingFieldSupport(); | |
| 60 | ||
| 61 | /** | |
| 62 | * {@inheritDoc} | |
| 63 | */ | |
| 64 | public <T> T load(Class<T> fixedFormatRecordClass, String data) { | |
| 65 | HashMap<String, Object> foundData = new HashMap<String, Object>(); | |
| 66 | HashMap<String, Class<?>> methodClass = new HashMap<String, Class<?>>(); | |
| 67 | getAndAssertRecordAnnotation(fixedFormatRecordClass); | |
| 68 |
1
1. load : removed call to com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::validatePatterns → SURVIVED |
validatePatterns(fixedFormatRecordClass); |
| 69 | ||
| 70 | T instance = recordInstantiator.instantiate(fixedFormatRecordClass); | |
| 71 | ||
| 72 | for (AnnotationTarget target : annotationScanner.scan(fixedFormatRecordClass)) { | |
| 73 | String methodName = annotationScanner.stripMethodPrefix(target.getter.getName()); | |
| 74 | Field fieldAnnotation = target.annotationSource.getAnnotation(Field.class); | |
| 75 | Fields fieldsAnnotation = target.annotationSource.getAnnotation(Fields.class); | |
| 76 |
1
1. load : negated conditional → KILLED |
if (fieldAnnotation != null) { |
| 77 |
1
1. load : removed call to com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::readFieldData → KILLED |
readFieldData(fixedFormatRecordClass, data, foundData, methodClass, target, methodName, fieldAnnotation); |
| 78 |
1
1. load : negated conditional → KILLED |
} else if (fieldsAnnotation != null) { |
| 79 |
2
1. load : negated conditional → KILLED 2. load : negated conditional → KILLED |
if (fieldsAnnotation.value() == null || fieldsAnnotation.value().length == 0) { |
| 80 | throw new FixedFormatException(format("%s annotation must contain minimum one %s annotation", Fields.class.getName(), Field.class.getName())); | |
| 81 | } | |
| 82 |
1
1. load : removed call to com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::readFieldData → KILLED |
readFieldData(fixedFormatRecordClass, data, foundData, methodClass, target, methodName, fieldsAnnotation.value()[0]); |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | Set<String> keys = foundData.keySet(); | |
| 87 | for (String key : keys) { | |
| 88 | String setterMethodName = "set" + key; | |
| 89 | Object foundDataObj = foundData.get(key); | |
| 90 |
1
1. load : negated conditional → KILLED |
if (foundDataObj != null) { |
| 91 | Class<?> datatype = methodClass.get(key); | |
| 92 | Method method; | |
| 93 | try { | |
| 94 | method = fixedFormatRecordClass.getMethod(setterMethodName, datatype); | |
| 95 | } catch (NoSuchMethodException e) { | |
| 96 | throw new FixedFormatException(format("setter method named %s.%s(%s) does not exist", fixedFormatRecordClass.getName(), setterMethodName, datatype)); | |
| 97 | } | |
| 98 | try { | |
| 99 | method.invoke(instance, foundData.get(key)); | |
| 100 | } catch (Exception e) { | |
| 101 | throw new FixedFormatException(format("could not invoke method %s.%s(%s)", fixedFormatRecordClass.getName(), setterMethodName, datatype), e); | |
| 102 | } | |
| 103 | } | |
| 104 | } | |
| 105 |
1
1. load : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::load → KILLED |
return instance; |
| 106 | } | |
| 107 | ||
| 108 | private <T> void readFieldData(Class<T> fixedFormatRecordClass, String data, HashMap<String, Object> foundData, HashMap<String, Class<?>> methodClass, AnnotationTarget target, String methodName, Field fieldAnnotation) { | |
| 109 | Object loadedData = readDataAccordingFieldAnnotation(fixedFormatRecordClass, data, target.getter, target.annotationSource, fieldAnnotation); | |
| 110 | foundData.put(methodName, loadedData); | |
| 111 | methodClass.put(methodName, target.getter.getReturnType()); | |
| 112 | } | |
| 113 | ||
| 114 | /** | |
| 115 | * {@inheritDoc} | |
| 116 | */ | |
| 117 | public <T> String export(String template, T fixedFormatRecord) { | |
| 118 | StringBuffer result = new StringBuffer(template); | |
| 119 | Record record = getAndAssertRecordAnnotation(fixedFormatRecord.getClass()); | |
| 120 |
1
1. export : removed call to com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::validatePatterns → KILLED |
validatePatterns(fixedFormatRecord.getClass()); |
| 121 | ||
| 122 | HashMap<Integer, String> foundData = new HashMap<Integer, String>(); | |
| 123 | for (AnnotationTarget target : annotationScanner.scan(fixedFormatRecord.getClass())) { | |
| 124 | Field fieldAnnotation = target.annotationSource.getAnnotation(Field.class); | |
| 125 | Fields fieldsAnnotation = target.annotationSource.getAnnotation(Fields.class); | |
| 126 |
1
1. export : negated conditional → KILLED |
if (fieldAnnotation != null) { |
| 127 |
2
1. export : negated conditional → KILLED 2. export : changed conditional boundary → KILLED |
if (fieldAnnotation.count() > 1) { |
| 128 |
1
1. export : removed call to com/ancientprogramming/fixedformat4j/format/impl/RepeatingFieldSupport::export → KILLED |
repeatingFieldSupport.export(fixedFormatRecord, target, fieldAnnotation, foundData); |
| 129 | } else { | |
| 130 | foundData.put(fieldAnnotation.offset(), exportDataAccordingFieldAnnotation(fixedFormatRecord, target, fieldAnnotation)); | |
| 131 | } | |
| 132 |
1
1. export : negated conditional → KILLED |
} else if (fieldsAnnotation != null) { |
| 133 | for (Field field : fieldsAnnotation.value()) { | |
| 134 | foundData.put(field.offset(), exportDataAccordingFieldAnnotation(fixedFormatRecord, target, field)); | |
| 135 | } | |
| 136 | } | |
| 137 | } | |
| 138 | ||
| 139 | for (Integer offset : foundData.keySet()) { | |
| 140 |
1
1. export : removed call to com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::appendData → KILLED |
appendData(result, record.paddingChar(), offset, foundData.get(offset)); |
| 141 | } | |
| 142 | ||
| 143 |
1
1. export : negated conditional → KILLED |
if (record.length() != -1) { |
| 144 |
2
1. export : negated conditional → KILLED 2. export : changed conditional boundary → KILLED |
while (result.length() < record.length()) { |
| 145 | result.append(record.paddingChar()); | |
| 146 | } | |
| 147 | } | |
| 148 |
1
1. export : replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::export → KILLED |
return result.toString(); |
| 149 | } | |
| 150 | ||
| 151 | /** | |
| 152 | * {@inheritDoc} | |
| 153 | */ | |
| 154 | public <T> String export(T fixedFormatRecord) { | |
| 155 |
1
1. export : replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::export → KILLED |
return export("", fixedFormatRecord); |
| 156 | } | |
| 157 | ||
| 158 | private void validatePatterns(Class<?> recordClass) { | |
| 159 |
1
1. validatePatterns : negated conditional → KILLED |
if (VALIDATED_CLASSES.contains(recordClass)) { |
| 160 | return; | |
| 161 | } | |
| 162 | for (AnnotationTarget target : annotationScanner.scan(recordClass)) { | |
| 163 | Field fieldAnnotation = target.annotationSource.getAnnotation(Field.class); | |
| 164 | Fields fieldsAnnotation = target.annotationSource.getAnnotation(Fields.class); | |
| 165 |
1
1. validatePatterns : negated conditional → KILLED |
if (fieldAnnotation != null) { |
| 166 |
1
1. validatePatterns : removed call to com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::validateFieldPattern → KILLED |
validateFieldPattern(target, fieldAnnotation); |
| 167 |
1
1. validatePatterns : negated conditional → SURVIVED |
} else if (fieldsAnnotation != null) { |
| 168 | for (Field field : fieldsAnnotation.value()) { | |
| 169 |
1
1. validatePatterns : removed call to com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::validateFieldPattern → SURVIVED |
validateFieldPattern(target, field); |
| 170 | } | |
| 171 | } | |
| 172 | } | |
| 173 | VALIDATED_CLASSES.add(recordClass); | |
| 174 | } | |
| 175 | ||
| 176 | private void validateFieldPattern(AnnotationTarget target, Field fieldAnnotation) { | |
| 177 | Class<?> datatype = instructionsBuilder.datatype(target.getter, fieldAnnotation); | |
| 178 | FixedFormatPattern patternAnnotation = target.annotationSource.getAnnotation(FixedFormatPattern.class); | |
| 179 | String pattern; | |
| 180 |
1
1. validateFieldPattern : negated conditional → KILLED |
if (patternAnnotation != null) { |
| 181 | pattern = patternAnnotation.value(); | |
| 182 |
1
1. validateFieldPattern : negated conditional → SURVIVED |
} else if (java.time.LocalDate.class.equals(datatype)) { |
| 183 | pattern = FixedFormatPatternData.LOCALDATE_DEFAULT.getPattern(); | |
| 184 |
1
1. validateFieldPattern : negated conditional → SURVIVED |
} else if (java.time.LocalDateTime.class.equals(datatype)) { |
| 185 | pattern = FixedFormatPatternData.DATETIME_DEFAULT.getPattern(); | |
| 186 | } else { | |
| 187 | pattern = FixedFormatPatternData.DEFAULT.getPattern(); | |
| 188 | } | |
| 189 |
1
1. validateFieldPattern : removed call to com/ancientprogramming/fixedformat4j/format/impl/PatternValidator::validate → KILLED |
PatternValidator.validate(datatype, pattern); |
| 190 | } | |
| 191 | ||
| 192 | private void appendData(StringBuffer result, Character paddingChar, Integer offset, String data) { | |
| 193 |
1
1. appendData : Replaced integer subtraction with addition → KILLED |
int zeroBasedOffset = offset - 1; |
| 194 |
2
1. appendData : negated conditional → TIMED_OUT 2. appendData : changed conditional boundary → KILLED |
while (result.length() < zeroBasedOffset) { |
| 195 | result.append(paddingChar); | |
| 196 | } | |
| 197 | int length = data.length(); | |
| 198 |
3
1. appendData : negated conditional → SURVIVED 2. appendData : Replaced integer addition with subtraction → SURVIVED 3. appendData : changed conditional boundary → SURVIVED |
if (result.length() < zeroBasedOffset + length) { |
| 199 |
2
1. appendData : Replaced integer addition with subtraction → SURVIVED 2. appendData : Replaced integer subtraction with addition → KILLED |
result.append(StringUtils.leftPad("", (zeroBasedOffset + length) - result.length(), paddingChar)); |
| 200 | } | |
| 201 |
1
1. appendData : Replaced integer addition with subtraction → KILLED |
result.replace(zeroBasedOffset, zeroBasedOffset + length, data); |
| 202 | } | |
| 203 | ||
| 204 | private <T> Record getAndAssertRecordAnnotation(Class<T> fixedFormatRecordClass) { | |
| 205 | Record recordAnno = fixedFormatRecordClass.getAnnotation(Record.class); | |
| 206 |
1
1. getAndAssertRecordAnnotation : negated conditional → KILLED |
if (recordAnno == null) { |
| 207 | throw new FixedFormatException(format("%s has to be marked with the record annotation to be loaded", fixedFormatRecordClass.getName())); | |
| 208 | } | |
| 209 |
1
1. getAndAssertRecordAnnotation : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::getAndAssertRecordAnnotation → KILLED |
return recordAnno; |
| 210 | } | |
| 211 | ||
| 212 | @SuppressWarnings({"unchecked"}) | |
| 213 | protected <T> Object readDataAccordingFieldAnnotation(Class<T> clazz, String data, Method getter, AnnotatedElement annotationSource, Field fieldAnno) throws ParseException { | |
| 214 |
1
1. readDataAccordingFieldAnnotation : removed call to com/ancientprogramming/fixedformat4j/format/impl/RepeatingFieldSupport::validateCount → KILLED |
repeatingFieldSupport.validateCount(getter, fieldAnno); |
| 215 | ||
| 216 |
2
1. readDataAccordingFieldAnnotation : negated conditional → KILLED 2. readDataAccordingFieldAnnotation : changed conditional boundary → KILLED |
if (fieldAnno.count() > 1) { |
| 217 |
1
1. readDataAccordingFieldAnnotation : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::readDataAccordingFieldAnnotation → KILLED |
return repeatingFieldSupport.read(clazz, data, getter, annotationSource, fieldAnno); |
| 218 | } | |
| 219 | ||
| 220 | Class<?> datatype = instructionsBuilder.datatype(getter, fieldAnno); | |
| 221 | ||
| 222 | FormatContext<?> context = instructionsBuilder.context(datatype, fieldAnno); | |
| 223 | FixedFormatter<?> formatter = getFixedFormatterInstance(context.getFormatter(), context); | |
| 224 | FormatInstructions formatdata = instructionsBuilder.build(annotationSource, fieldAnno, datatype); | |
| 225 | ||
| 226 | String dataToParse = fetchData(data, formatdata, context); | |
| 227 | ||
| 228 | Object loadedData; | |
| 229 | ||
| 230 | Annotation recordAnno = datatype.getAnnotation(Record.class); | |
| 231 |
1
1. readDataAccordingFieldAnnotation : negated conditional → KILLED |
if (recordAnno != null) { |
| 232 | loadedData = load(datatype, dataToParse); | |
| 233 | } else { | |
| 234 | try { | |
| 235 | loadedData = formatter.parse(dataToParse, formatdata); | |
| 236 | } catch (RuntimeException e) { | |
| 237 | throw new ParseException(data, dataToParse, clazz, getter, context, formatdata, e); | |
| 238 | } | |
| 239 | } | |
| 240 | if (LOG.isDebugEnabled()) { | |
| 241 | LOG.debug("the loaded data[{}]", loadedData); | |
| 242 | } | |
| 243 |
1
1. readDataAccordingFieldAnnotation : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::readDataAccordingFieldAnnotation → KILLED |
return loadedData; |
| 244 | } | |
| 245 | ||
| 246 | @SuppressWarnings({"unchecked"}) | |
| 247 | private <T> String exportDataAccordingFieldAnnotation(T fixedFormatRecord, AnnotationTarget target, Field fieldAnno) { | |
| 248 |
1
1. exportDataAccordingFieldAnnotation : removed call to com/ancientprogramming/fixedformat4j/format/impl/RepeatingFieldSupport::validateCount → SURVIVED |
repeatingFieldSupport.validateCount(target.getter, fieldAnno); |
| 249 | ||
| 250 | Class<?> datatype = instructionsBuilder.datatype(target.getter, fieldAnno); | |
| 251 | ||
| 252 | FormatContext<?> context = instructionsBuilder.context(datatype, fieldAnno); | |
| 253 | FixedFormatter<?> formatter = getFixedFormatterInstance(context.getFormatter(), context); | |
| 254 | FormatInstructions formatdata = instructionsBuilder.build(target.annotationSource, fieldAnno, datatype); | |
| 255 | Object valueObject; | |
| 256 | try { | |
| 257 | valueObject = target.getter.invoke(fixedFormatRecord); | |
| 258 | } catch (Exception e) { | |
| 259 | throw new FixedFormatException(format("could not invoke method %s.%s(%s)", fixedFormatRecord.getClass().getName(), target.getter.getName(), datatype), e); | |
| 260 | } | |
| 261 | ||
| 262 | String result; | |
| 263 |
2
1. exportDataAccordingFieldAnnotation : negated conditional → KILLED 2. exportDataAccordingFieldAnnotation : negated conditional → KILLED |
if (valueObject != null && valueObject.getClass().getAnnotation(Record.class) != null) { |
| 264 | result = export(valueObject); | |
| 265 | } else { | |
| 266 | result = ((FixedFormatter<Object>) formatter).format(valueObject, formatdata); | |
| 267 | } | |
| 268 | if (LOG.isDebugEnabled()) { | |
| 269 | LOG.debug(format("exported %s ", result)); | |
| 270 | } | |
| 271 |
1
1. exportDataAccordingFieldAnnotation : replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl::exportDataAccordingFieldAnnotation → KILLED |
return result; |
| 272 | } | |
| 273 | } | |
Mutations | ||
| 68 |
1.1 |
|
| 76 |
1.1 |
|
| 77 |
1.1 |
|
| 78 |
1.1 |
|
| 79 |
1.1 2.2 |
|
| 82 |
1.1 |
|
| 90 |
1.1 |
|
| 105 |
1.1 |
|
| 120 |
1.1 |
|
| 126 |
1.1 |
|
| 127 |
1.1 2.2 |
|
| 128 |
1.1 |
|
| 132 |
1.1 |
|
| 140 |
1.1 |
|
| 143 |
1.1 |
|
| 144 |
1.1 2.2 |
|
| 148 |
1.1 |
|
| 155 |
1.1 |
|
| 159 |
1.1 |
|
| 165 |
1.1 |
|
| 166 |
1.1 |
|
| 167 |
1.1 |
|
| 169 |
1.1 |
|
| 180 |
1.1 |
|
| 182 |
1.1 |
|
| 184 |
1.1 |
|
| 189 |
1.1 |
|
| 193 |
1.1 |
|
| 194 |
1.1 2.2 |
|
| 198 |
1.1 2.2 3.3 |
|
| 199 |
1.1 2.2 |
|
| 201 |
1.1 |
|
| 206 |
1.1 |
|
| 209 |
1.1 |
|
| 214 |
1.1 |
|
| 216 |
1.1 2.2 |
|
| 217 |
1.1 |
|
| 231 |
1.1 |
|
| 243 |
1.1 |
|
| 248 |
1.1 |
|
| 263 |
1.1 2.2 |
|
| 271 |
1.1 |