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 public String getValue() { 061 parse(); 062 063 return formatResult(value().map(v -> Long.toString(v)) 064 .orElse(valueProvider.getValue().orElse(""))); 065 } 066 067 private void parse() { 068 final Optional<String> value = valueProvider.getValue(); 069 070 ImmutableList.Builder<NumberElement> numberELementBuilder = ImmutableList.builder(); 071 ImmutableList.Builder<NumberElement> numberIndexBuilder = ImmutableList.builder(); 072 073 if (value.isPresent()) { 074 String numberValue = value.get(); 075 if (!numberValue.isBlank()) { 076 StringBuilder sb = new StringBuilder(); 077 int charIndex = 0; 078 boolean number = Character.isDigit(numberValue.charAt(charIndex)); 079 080 while (charIndex < numberValue.length()) { 081 char currentChar = numberValue.charAt(charIndex); 082 if (number != Character.isDigit(currentChar)) { 083 var numberElement = new NumberElement(number, sb.toString()); 084 085 numberELementBuilder.add(numberElement); 086 if (number) { 087 numberIndexBuilder.add(numberElement); 088 } 089 090 number = !number; 091 sb.setLength(0); 092 } 093 sb.append(currentChar); 094 charIndex++; 095 } 096 if (sb.length() > 0) { 097 var numberElement = new NumberElement(number, sb.toString()); 098 099 numberELementBuilder.add(numberElement); 100 if (number) { 101 numberIndexBuilder.add(numberElement); 102 } 103 } 104 } 105 } 106 107 this.numberElements = numberELementBuilder.build(); 108 this.numberIndex = numberIndexBuilder.build(); 109 } 110 111 private String print() { 112 return Joiner.on("").join(numberElements.stream().map(NumberElement::getFieldValue).iterator()); 113 } 114 115 private Optional<Long> value() { 116 return fieldDefinition.getFieldNumber() 117 .map(fieldNumber -> numberIndex.get(fieldNumber)) 118 .flatMap(NumberElement::getLongValue); 119 } 120 121 private void set(long value) { 122 Optional<NumberElement> numberElement = fieldDefinition.getFieldNumber() 123 .map(fieldNumber -> numberIndex.get(fieldNumber)); 124 125 if (numberElement.isPresent()) { 126 numberElement.get().setLongValue(value); 127 valueProvider.setValue(print()); 128 } else { 129 valueProvider.setValue(Long.toString(value)); 130 } 131 } 132 133 public void increment() { 134 parse(); 135 136 fieldDefinition.getFieldNumber().ifPresent(fieldNumber -> checkState(numberIndex.size() > fieldNumber, 137 "Only %s fields in %s, field %s requested.", numberElements.size(), print(), fieldNumber)); 138 139 value().ifPresent(value -> { 140 set(value + fieldDefinition.getIncrement()); 141 }); 142 } 143 144 public Optional<Long> getNumberValue() { 145 parse(); 146 147 fieldDefinition.getFieldNumber().ifPresent(fieldNumber -> checkState(numberIndex.size() > fieldNumber, 148 "Only %s fields in %s, field %s requested.", numberElements.size(), print(), fieldNumber)); 149 150 return value(); 151 } 152 153 @Override 154 public String toString() { 155 return new StringJoiner(", ", NumberField.class.getSimpleName() + "[", "]") 156 .add("valueProvider=" + valueProvider) 157 .add("numberElements=" + numberElements) 158 .add("numberIndex=" + numberIndex) 159 .add("fieldDefinition=" + fieldDefinition) 160 .toString(); 161 } 162 163 private static final class NumberElement { 164 165 private final boolean number; 166 private String value; 167 168 private NumberElement(boolean number, String value) { 169 this.number = number; 170 this.value = value; 171 } 172 173 String getFieldValue() { 174 return value; 175 } 176 177 void setLongValue(long value) { 178 this.value = Long.toString(value); 179 } 180 181 Optional<Long> getLongValue() { 182 return number ? Optional.of(Long.parseLong(value)) : Optional.empty(); 183 } 184 185 @Override 186 public String toString() { 187 return new StringJoiner(", ", NumberElement.class.getSimpleName() + "[", "]") 188 .add("number=" + number) 189 .add("value='" + value + "'") 190 .toString(); 191 } 192 } 193}