View Javadoc
1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  
15  package org.basepom.mojo.propertyhelper.fields;
16  
17  import static com.google.common.base.Preconditions.checkState;
18  
19  import org.basepom.mojo.propertyhelper.Field;
20  import org.basepom.mojo.propertyhelper.FieldContext;
21  import org.basepom.mojo.propertyhelper.ValueProvider;
22  import org.basepom.mojo.propertyhelper.definitions.NumberDefinition;
23  
24  import java.util.List;
25  import java.util.Optional;
26  import java.util.StringJoiner;
27  
28  import com.google.common.annotations.VisibleForTesting;
29  import com.google.common.base.Joiner;
30  import com.google.common.collect.ImmutableList;
31  
32  public final class NumberField extends Field<String, NumberDefinition> {
33  
34      private final ValueProvider valueProvider;
35  
36      // internal state
37      private List<NumberElement> numberElements;
38      private List<NumberElement> numberIndex;
39  
40      @VisibleForTesting
41      public static NumberField forTesting(NumberDefinition numberDefinition, ValueProvider valueProvider) {
42          return new NumberField(numberDefinition, valueProvider, FieldContext.forTesting());
43      }
44  
45      public NumberField(final NumberDefinition fieldDefinition, final ValueProvider valueProvider,
46          FieldContext fieldContext) {
47          super(fieldDefinition, fieldContext);
48  
49          this.valueProvider = valueProvider;
50      }
51  
52      @Override
53      public String getFieldName() {
54          // This is not the property name (because many definitions can map onto one prop)
55          // but the actual id.
56          return fieldDefinition.getId();
57      }
58  
59      @Override
60      @SuppressWarnings("PMD.LambdaCanBeMethodReference") // https://github.com/pmd/pmd/issues/5043
61      public String getValue() {
62          parse();
63  
64          return formatResult(value().map(v -> Long.toString(v))
65              .orElse(valueProvider.getValue().orElse("")));
66      }
67  
68      private void parse() {
69          final Optional<String> value = valueProvider.getValue();
70  
71          ImmutableList.Builder<NumberElement> numberELementBuilder = ImmutableList.builder();
72          ImmutableList.Builder<NumberElement> numberIndexBuilder = ImmutableList.builder();
73  
74          if (value.isPresent()) {
75              String numberValue = value.get();
76              if (!numberValue.isBlank()) {
77                  StringBuilder sb = new StringBuilder();
78                  int charIndex = 0;
79                  boolean number = Character.isDigit(numberValue.charAt(charIndex));
80  
81                  while (charIndex < numberValue.length()) {
82                      char currentChar = numberValue.charAt(charIndex);
83                      if (number != Character.isDigit(currentChar)) {
84                          var numberElement = new NumberElement(number, sb.toString());
85  
86                          numberELementBuilder.add(numberElement);
87                          if (number) {
88                              numberIndexBuilder.add(numberElement);
89                          }
90  
91                          number = !number;
92                          sb.setLength(0);
93                      }
94                      sb.append(currentChar);
95                      charIndex++;
96                  }
97                  if (sb.length() > 0) {
98                      var numberElement = new NumberElement(number, sb.toString());
99  
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 }