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      public String getValue() {
61          parse();
62  
63          return formatResult(value().map(v -> Long.toString(v))
64              .orElse(valueProvider.getValue().orElse("")));
65      }
66  
67      private void parse() {
68          final Optional<String> value = valueProvider.getValue();
69  
70          ImmutableList.Builder<NumberElement> numberELementBuilder = ImmutableList.builder();
71          ImmutableList.Builder<NumberElement> numberIndexBuilder = ImmutableList.builder();
72  
73          if (value.isPresent()) {
74              String numberValue = value.get();
75              if (!numberValue.isBlank()) {
76                  StringBuilder sb = new StringBuilder();
77                  int charIndex = 0;
78                  boolean number = Character.isDigit(numberValue.charAt(charIndex));
79  
80                  while (charIndex < numberValue.length()) {
81                      char currentChar = numberValue.charAt(charIndex);
82                      if (number != Character.isDigit(currentChar)) {
83                          var numberElement = new NumberElement(number, sb.toString());
84  
85                          numberELementBuilder.add(numberElement);
86                          if (number) {
87                              numberIndexBuilder.add(numberElement);
88                          }
89  
90                          number = !number;
91                          sb.setLength(0);
92                      }
93                      sb.append(currentChar);
94                      charIndex++;
95                  }
96                  if (sb.length() > 0) {
97                      var numberElement = new NumberElement(number, sb.toString());
98  
99                      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 }