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 net.kyori.adventure.text.Component; 020import net.kyori.adventure.text.ComponentLike; 021import net.kyori.adventure.text.format.NamedTextColor; 022import net.kyori.adventure.text.format.TextColor; 023import net.kyori.adventure.text.format.TextDecoration; 024import org.checkerframework.checker.nullness.qual.NonNull; 025import org.checkerframework.checker.nullness.qual.Nullable; 026 027import static net.kyori.adventure.text.Component.text; 028import static net.kyori.adventure.text.format.Style.style; 029 030/** 031 * A (key, value) pair for one specific context entry. 032 * 033 * <p>This value holds both raw and parsed context values.</p> 034 * 035 * @param <V> value type 036 */ 037public final class ContextValue<V> implements ComponentLike { 038 private static final Component JOINER = text("="); 039 040 private final String key; 041 private final String rawValue; 042 private @Nullable ContextDefinition<V> definition; 043 private @Nullable V parsedValue; 044 045 public ContextValue(final String key, final String rawValue) { 046 this.key = key; 047 this.rawValue = rawValue; 048 } 049 050 ContextValue(final ContextDefinition<V> def, final V value) { 051 this(def.name(), def.serialize(value)); 052 this.definition = def; 053 this.parsedValue = value; 054 } 055 056 /** 057 * Get the key used to resolve a context value. 058 * 059 * @return value key 060 */ 061 public String key() { 062 return this.key; 063 } 064 065 /** 066 * The raw value, before being deserialized by a context definition. 067 * 068 * @return raw value, as provided by the user 069 */ 070 public String rawValue() { 071 return this.rawValue; 072 } 073 074 public @Nullable ContextDefinition<V> definition() { 075 return this.definition; 076 } 077 078 public @Nullable V parsedValue() { 079 return this.parsedValue; 080 } 081 082 @SuppressWarnings("unchecked") 083 public boolean tryResolve(final ContextDefinitionProvider provider) { 084 if (this.definition != null && !(this.definition instanceof SimpleContextDefinition.Fallback)) { 085 return this.parsedValue != null; 086 } 087 final @Nullable ContextDefinition<V> definition = (ContextDefinition<V>) provider.contextDefinition(this.key); 088 if (definition != null) { 089 this.definition = definition; 090 this.parsedValue = definition.deserialize(this.rawValue); 091 return this.parsedValue != null; 092 } 093 return false; 094 } 095 096 public V getParsedValue(final ContextDefinition<V> definition) { 097 if (this.definition != null && !this.definition.equals(definition)) { 098 throw new IllegalStateException("The provided context definition does not match the one this context object currently knows about"); 099 } 100 101 this.definition = definition; 102 @Nullable V parsedValue = this.parsedValue; 103 if (parsedValue == null) { 104 parsedValue = definition.deserialize(this.rawValue); 105 this.parsedValue = parsedValue; 106 } 107 if (parsedValue == null) { 108 throw new IllegalArgumentException("Invalid value provided for context " + definition.name()); 109 } 110 return parsedValue; 111 } 112 113 @SuppressWarnings("unchecked") 114 public V getParsedValue(final ContextDefinitionProvider provider) { 115 @Nullable V tempParsed = parsedValue; 116 if (tempParsed != null) { 117 return tempParsed; 118 } 119 120 final @Nullable ContextDefinition<?> def = provider.contextDefinition(this.key); 121 tempParsed = def == null ? null : (V) def.deserialize(this.rawValue); 122 if (tempParsed == null) { 123 throw new RuntimeException("No definition for context " + this.key); 124 } 125 126 parsedValue = tempParsed; 127 return tempParsed; 128 } 129 130 @Override 131 public boolean equals(final @Nullable Object other) { 132 if (this == other) return true; 133 if (!(other instanceof ContextValue<?>)) return false; 134 135 final ContextValue<?> that = (ContextValue<?>) other; 136 if (!this.key.equals(that.key)) return false; 137 if (!this.rawValue.equals(that.rawValue)) return false; 138 139 return true; 140 } 141 142 @Override 143 public int hashCode() { 144 int result = this.key.hashCode(); 145 result = 31 * result + this.rawValue.hashCode(); 146 return result; 147 } 148 149 @Override 150 public String toString() { 151 return this.key + ":" + this.parsedValue + " (raw: " + this.rawValue + ")"; 152 } 153 154 @Override 155 public @NonNull Component asComponent() { 156 if (this.parsedValue != null) { 157 return text() 158 .content(this.key) 159 .append(JOINER) 160 .append(text(this.parsedValue.toString())) 161 .hoverEvent(text("(raw: " + this.rawValue + ")")) 162 .build(); 163 } else { 164 return text() 165 .content(this.key) 166 .append(JOINER) 167 .append(text(this.rawValue)) 168 .hoverEvent(text("(unresolved)", style(TextDecoration.BOLD, NamedTextColor.RED))) 169 .build(); 170 } 171 } 172 173}