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.checkNotNull;
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.UuidDefinition;
023
024import java.util.Optional;
025import java.util.Random;
026import java.util.StringJoiner;
027import java.util.UUID;
028
029import com.google.common.annotations.VisibleForTesting;
030
031public final class UuidField extends Field<String, UuidDefinition> {
032
033    private final ValueProvider valueProvider;
034    private final Random secureRandom;
035
036    @VisibleForTesting
037    public static UuidField forTesting(UuidDefinition uuidDefinition, ValueProvider valueProvider) {
038        return new UuidField(uuidDefinition, valueProvider, FieldContext.forTesting());
039    }
040
041    public UuidField(final UuidDefinition uuidDefinition, final ValueProvider valueProvider,
042        FieldContext fieldContext) {
043        super(uuidDefinition, fieldContext);
044
045        this.valueProvider = checkNotNull(valueProvider, "valueProvider is null");
046
047        this.secureRandom = fieldContext.getRandom();
048    }
049
050    @Override
051    public String getFieldName() {
052        return fieldDefinition.getId();
053    }
054
055    @Override
056    public String getValue() {
057        final Optional<String> propValue = valueProvider.getValue();
058
059        // Only add the value from the provider if it is not null.
060        UUID result = propValue.map(UUID::fromString)
061            .orElse(fieldDefinition.getValue()
062                .orElseGet(this::createRandomUUID));
063
064        valueProvider.setValue(result.toString());
065        return formatResult(result.toString());
066    }
067
068    private UUID createRandomUUID() {
069        long upperValue = secureRandom.nextLong();
070        long lowerValue = secureRandom.nextLong();
071
072        // UUID v4 (random) - see https://datatracker.ietf.org/doc/html/rfc4122#section-4.4
073        lowerValue &= 0xff0fffffffffffffL; // clear top four bits of time_hi_and_version
074        lowerValue |= 0x0040000000000000L; // set to 0100 (UUID v4)
075        upperValue &= 0xffffffffffffff3fL; // clear top two bits of clock_seq_hi_and_reserved
076        upperValue |= 0x0000000000000080L; // set to 1 and 0
077
078        return new UUID(upperValue, lowerValue);
079    }
080
081
082    @Override
083    public String toString() {
084        return new StringJoiner(", ", UuidField.class.getSimpleName() + "[", "]")
085            .add("uuidDefinition=" + fieldDefinition)
086            .add("valueProvider=" + valueProvider)
087            .toString();
088    }
089}