001/*
002 * PermissionsEx
003 * Copyright (C) zml and PermissionsEx contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package ca.stellardrift.permissionsex.context;
018
019import org.checkerframework.checker.nullness.qual.Nullable;
020
021/**
022 * A (key, value) pair for one specific context entry.
023 *
024 * <p>This value holds both raw and parsed context values.</p>
025 *
026 * @param <V> value type
027 */
028public final class ContextValue<V> {
029    private final String key;
030    private final String rawValue;
031    private @Nullable ContextDefinition<V> definition;
032    private @Nullable V parsedValue;
033
034    public ContextValue(final String key, final String rawValue) {
035        this.key = key;
036        this.rawValue = rawValue;
037    }
038
039    ContextValue(final ContextDefinition<V> def, final V value) {
040        this(def.name(), def.serialize(value));
041        this.definition = def;
042        this.parsedValue = value;
043    }
044
045    /**
046     * Get the key used to resolve a context value.
047     *
048     * @return value key
049     */
050    public String key() {
051        return this.key;
052    }
053
054    /**
055     * The raw value, before being deserialized by a context definition.
056     *
057     * @return raw value, as provided by the user
058     */
059    public String rawValue() {
060        return this.rawValue;
061    }
062
063    public @Nullable ContextDefinition<V> definition() {
064        return this.definition;
065    }
066
067    public @Nullable V parsedValue() {
068        return this.parsedValue;
069    }
070
071    @SuppressWarnings("unchecked")
072    public boolean tryResolve(final ContextDefinitionProvider provider) {
073        if (this.definition != null && !(this.definition instanceof SimpleContextDefinition.Fallback)) {
074            return this.parsedValue != null;
075        }
076        final @Nullable ContextDefinition<V> definition = (ContextDefinition<V>) provider.contextDefinition(this.key);
077        if (definition != null) {
078            this.definition = definition;
079            this.parsedValue = definition.deserialize(this.rawValue);
080            return this.parsedValue != null;
081        }
082        return false;
083    }
084
085    public V getParsedValue(final ContextDefinition<V> definition) {
086        if (this.definition != null && !this.definition.equals(definition)) {
087            throw new IllegalStateException("The provided context definition does not match the one this context object currently knows about");
088        }
089
090        this.definition = definition;
091        @Nullable V parsedValue = this.parsedValue;
092        if (parsedValue == null) {
093            parsedValue = definition.deserialize(this.rawValue);
094            this.parsedValue = parsedValue;
095        }
096        if (parsedValue == null) {
097            throw new IllegalArgumentException("Invalid value provided for context " + definition.name());
098        }
099        return parsedValue;
100    }
101
102    @SuppressWarnings("unchecked")
103    public V getParsedValue(final ContextDefinitionProvider provider) {
104        @Nullable V tempParsed = parsedValue;
105        if (tempParsed != null) {
106            return tempParsed;
107        }
108
109        final @Nullable ContextDefinition<?> def = provider.contextDefinition(this.key);
110        tempParsed = def == null ? null : (V) def.deserialize(this.rawValue);
111        if (tempParsed == null) {
112            throw new RuntimeException("No definition for context " + this.key);
113        }
114
115        parsedValue = tempParsed;
116        return tempParsed;
117    }
118
119    @Override
120    public boolean equals(final @Nullable Object other) {
121        if (this == other) return true;
122        if (!(other instanceof ContextValue<?>)) return false;
123
124        final ContextValue<?> that = (ContextValue<?>) other;
125        if (!this.key.equals(that.key)) return false;
126        if (!this.rawValue.equals(that.rawValue)) return false;
127
128        return true;
129    }
130
131    @Override
132    public int hashCode() {
133        int result = this.key.hashCode();
134        result = 31 * result + this.rawValue.hashCode();
135        return result;
136    }
137
138    @Override
139    public String toString() {
140        return this.key + ":" + this.parsedValue + " (raw: " + this.rawValue + ")";
141    }
142}