001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014 015package org.basepom.mojo.propertyhelper.fields; 016 017import static com.google.common.base.Preconditions.checkState; 018 019import org.basepom.mojo.propertyhelper.Field; 020import org.basepom.mojo.propertyhelper.FieldContext; 021import org.basepom.mojo.propertyhelper.ValueProvider; 022import org.basepom.mojo.propertyhelper.definitions.NumberDefinition; 023 024import java.util.List; 025import java.util.Optional; 026import java.util.StringJoiner; 027 028import com.google.common.annotations.VisibleForTesting; 029import com.google.common.base.Joiner; 030import com.google.common.collect.ImmutableList; 031 032public final class NumberField extends Field<String, NumberDefinition> { 033 034 private final ValueProvider valueProvider; 035 036 // internal state 037 private List<NumberElement> numberElements; 038 private List<NumberElement> numberIndex; 039 040 @VisibleForTesting 041 public static NumberField forTesting(NumberDefinition numberDefinition, ValueProvider valueProvider) { 042 return new NumberField(numberDefinition, valueProvider, FieldContext.forTesting()); 043 } 044 045 public NumberField(final NumberDefinition fieldDefinition, final ValueProvider valueProvider, 046 FieldContext fieldContext) { 047 super(fieldDefinition, fieldContext); 048 049 this.valueProvider = valueProvider; 050 } 051 052 @Override 053 public String getFieldName() { 054 // This is not the property name (because many definitions can map onto one prop) 055 // but the actual id. 056 return fieldDefinition.getId(); 057 } 058 059 @Override 060 @SuppressWarnings("PMD.LambdaCanBeMethodReference") // https://github.com/pmd/pmd/issues/5043 061 public String getValue() { 062 parse(); 063 064 return formatResult(value().map(v -> Long.toString(v)) 065 .orElse(valueProvider.getValue().orElse(""))); 066 } 067 068 private void parse() { 069 final Optional<String> value = valueProvider.getValue(); 070 071 ImmutableList.Builder<NumberElement> numberELementBuilder = ImmutableList.builder(); 072 ImmutableList.Builder<NumberElement> numberIndexBuilder = ImmutableList.builder(); 073 074 if (value.isPresent()) { 075 String numberValue = value.get(); 076 if (!numberValue.isBlank()) { 077 StringBuilder sb = new StringBuilder(); 078 int charIndex = 0; 079 boolean number = Character.isDigit(numberValue.charAt(charIndex)); 080 081 while (charIndex < numberValue.length()) { 082 char currentChar = numberValue.charAt(charIndex); 083 if (number != Character.isDigit(currentChar)) { 084 var numberElement = new NumberElement(number, sb.toString()); 085 086 numberELementBuilder.add(numberElement); 087 if (number) { 088 numberIndexBuilder.add(numberElement); 089 } 090 091 number = !number; 092 sb.setLength(0); 093 } 094 sb.append(currentChar); 095 charIndex++; 096 } 097 if (sb.length() > 0) { 098 var numberElement = new NumberElement(number, sb.toString()); 099 100 numberELementBuilder.add(numberElement); 101 if (number) { 102 numberIndexBuilder.add(numberElement); 103 } 104 } 105 } 106 } 107 108 this.numberElements = numberELementBuilder.build(); 109 this.numberIndex = numberIndexBuilder.build(); 110 } 111 112 private String print() { 113 return Joiner.on("").join(numberElements.stream().map(NumberElement::getFieldValue).iterator()); 114 } 115 116 private Optional<Long> value() { 117 return fieldDefinition.getFieldNumber() 118 .map(numberIndex::get) 119 .flatMap(NumberElement::getLongValue); 120 } 121 122 private void set(long value) { 123 Optional<NumberElement> numberElement = fieldDefinition.getFieldNumber() 124 .map(numberIndex::get); 125 126 if (numberElement.isPresent()) { 127 numberElement.get().setLongValue(value); 128 valueProvider.setValue(print()); 129 } else { 130 valueProvider.setValue(Long.toString(value)); 131 } 132 } 133 134 public void increment() { 135 parse(); 136 137 fieldDefinition.getFieldNumber().ifPresent(fieldNumber -> checkState(numberIndex.size() > fieldNumber, 138 "Only %s fields in %s, field %s requested.", numberElements.size(), print(), fieldNumber)); 139 140 value().ifPresent(value -> { 141 set(value + fieldDefinition.getIncrement()); 142 }); 143 } 144 145 public Optional<Long> getNumberValue() { 146 parse(); 147 148 fieldDefinition.getFieldNumber().ifPresent(fieldNumber -> checkState(numberIndex.size() > fieldNumber, 149 "Only %s fields in %s, field %s requested.", numberElements.size(), print(), fieldNumber)); 150 151 return value(); 152 } 153 154 @Override 155 public String toString() { 156 return new StringJoiner(", ", NumberField.class.getSimpleName() + "[", "]") 157 .add("valueProvider=" + valueProvider) 158 .add("numberElements=" + numberElements) 159 .add("numberIndex=" + numberIndex) 160 .add("fieldDefinition=" + fieldDefinition) 161 .toString(); 162 } 163 164 private static final class NumberElement { 165 166 private final boolean number; 167 private String value; 168 169 private NumberElement(boolean number, String value) { 170 this.number = number; 171 this.value = value; 172 } 173 174 String getFieldValue() { 175 return value; 176 } 177 178 void setLongValue(long value) { 179 this.value = Long.toString(value); 180 } 181 182 Optional<Long> getLongValue() { 183 return number ? Optional.of(Long.parseLong(value)) : Optional.empty(); 184 } 185 186 @Override 187 public String toString() { 188 return new StringJoiner(", ", NumberElement.class.getSimpleName() + "[", "]") 189 .add("number=" + number) 190 .add("value='" + value + "'") 191 .toString(); 192 } 193 } 194}